1- import React , { useEffect , useMemo , useState } from 'react'
1+ import React , { useCallback , useEffect , useMemo , useState } from 'react'
22import classNames from 'classnames'
33import { filesize } from 'filesize'
44import moment from 'moment'
@@ -21,6 +21,7 @@ import {
2121} from '../Dialog'
2222import SearchInputButton from '../SearchInput/SearchInputButton'
2323import { ClickableLink } from '../helpers/ClickableLink'
24+ import { useFetch } from '../../hooks/useFetch'
2425
2526const log = getLogger ( 'renderer/components/AppPicker' )
2627
@@ -70,7 +71,6 @@ type Props = {
7071
7172export function AppPicker ( { onAppSelected } : Props ) {
7273 const tx = useTranslationFunction ( )
73- const [ apps , setApps ] = useState < AppInfo [ ] > ( [ ] )
7474 const [ searchQuery , setSearchQuery ] = useState ( '' )
7575 const [ isOffline , setIsOffline ] = useState ( false )
7676 const [ selectedCategory , setSelectedCategory ] = useState ( AppCategoryEnum . home )
@@ -82,44 +82,45 @@ export function AppPicker({ onAppSelected }: Props) {
8282 AppCategoryEnum . game ,
8383 ]
8484
85- useEffect ( ( ) => {
86- const fetchApps = async ( ) => {
87- try {
88- const response = await BackendRemote . rpc . getHttpResponse (
89- selectedAccountId ( ) ,
90- AppStoreUrl + 'xdcget-lock.json'
91- )
92- const apps = getJsonFromBase64 ( response . blob ) as AppInfo [ ]
93- if ( apps === null ) return
94- apps . sort ( ( a : AppInfo , b : AppInfo ) => {
95- const dateA = new Date ( a . date )
96- const dateB = new Date ( b . date )
97- return dateB . getTime ( ) - dateA . getTime ( ) // Show newest first
98- } )
99- for ( const app of apps ) {
100- app . short_description = app . description . split ( '\n' ) [ 0 ]
101- app . description = app . description . split ( '\n' ) . slice ( 1 ) . join ( '\n' )
102- const url = new URL ( app . source_code_url )
103- app . author = url . pathname . split ( '/' ) [ 1 ]
104- app . date = moment ( app . date ) . format ( 'LL' )
105- }
106- setApps ( apps )
107- } catch ( error ) {
108- log . error ( 'Failed to fetch apps:' , error )
109- }
85+ const fetchApps = useCallback ( async ( ) => {
86+ // This may throw, e.g. on network error.
87+ const response = await BackendRemote . rpc . getHttpResponse (
88+ selectedAccountId ( ) ,
89+ AppStoreUrl + 'xdcget-lock.json'
90+ )
91+ const apps = getJsonFromBase64 ( response . blob ) as AppInfo [ ]
92+ if ( apps == null ) {
93+ throw new Error ( `Received \`null\` response from ${ AppStoreUrl } ` )
94+ }
95+ apps . sort ( ( a : AppInfo , b : AppInfo ) => {
96+ const dateA = new Date ( a . date )
97+ const dateB = new Date ( b . date )
98+ return dateB . getTime ( ) - dateA . getTime ( ) // Show newest first
99+ } )
100+ for ( const app of apps ) {
101+ app . short_description = app . description . split ( '\n' ) [ 0 ]
102+ app . description = app . description . split ( '\n' ) . slice ( 1 ) . join ( '\n' )
103+ const url = new URL ( app . source_code_url )
104+ app . author = url . pathname . split ( '/' ) [ 1 ]
105+ app . date = moment ( app . date ) . format ( 'LL' )
110106 }
111- fetchApps ( )
112- } , [ setApps ] )
107+ return apps
108+ } , [ ] )
109+ const appsFetch = useFetch ( fetchApps , [ ] )
110+ if ( appsFetch . result ?. ok === false ) {
111+ log . error ( 'Failed to fetch apps:' , appsFetch . result . err )
112+ }
113+ const apps = appsFetch . result ?. ok ? appsFetch . result . value : null
113114
114115 useEffect ( ( ) => {
115116 const loadIcons = async ( ) => {
116- if ( ! apps . length ) {
117+ if ( apps == null ) {
117118 const connectivity =
118119 await BackendRemote . rpc . getConnectivity ( selectedAccountId ( ) )
119120 if ( connectivity !== C . DC_CONNECTIVITY_CONNECTED ) {
120121 setIsOffline ( true )
121- return
122122 }
123+ return
123124 }
124125 const newIcons : { [ key : string ] : string } = { }
125126 setIcons ( newIcons )
@@ -144,6 +145,10 @@ export function AppPicker({ onAppSelected }: Props) {
144145 } , [ apps , isOffline ] )
145146
146147 const filteredApps = useMemo ( ( ) => {
148+ if ( apps == null ) {
149+ return null
150+ }
151+
147152 const lowerCaseQuery = searchQuery . toLowerCase ( )
148153 const findByRelevance = ( apps : AppInfo [ ] ) => {
149154 const queryEqualsAuthor = apps . filter (
@@ -302,7 +307,7 @@ export function AppPicker({ onAppSelected }: Props) {
302307 />
303308 ) }
304309 < div className = { styles . appPickerList } >
305- { ! isOffline && Object . keys ( apps ) . length > 0 ? (
310+ { ! isOffline && filteredApps != null ? (
306311 < >
307312 { selectedAppInfo && (
308313 < AppInfoOverlay
0 commit comments