diff --git a/src/components/Tokens/TokenTable/TokenTable.tsx b/src/components/Tokens/TokenTable/TokenTable.tsx index d0aa38d47a..ff2487a6c8 100644 --- a/src/components/Tokens/TokenTable/TokenTable.tsx +++ b/src/components/Tokens/TokenTable/TokenTable.tsx @@ -72,8 +72,7 @@ export default function TokenTable() { // TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName) - const { loading, tokens, tokensWithoutPriceHistoryCount, hasMore, loadMoreTokens, maxFetchable } = - useTopTokens(chainName) + const { error, loading, tokens, hasMore, loadMoreTokens, maxFetchable } = useTopTokens(chainName) const showMoreLoadingRows = Boolean(loading && hasMore) const observer = useRef() @@ -93,9 +92,9 @@ export default function TokenTable() { /* loading and error state */ if (loading && (!tokens || tokens?.length === 0)) { - return + return } else { - if (!tokens) { + if (error || !tokens) { return ( (topTokens100Query, { duration, chain }) -} - const topTokens100Query = graphql` query TopTokens100Query($duration: HistoryDuration!, $chain: Chain!) { topTokens(pageSize: 100, page: 1, chain: $chain) { @@ -166,29 +162,48 @@ const checkIfAllTokensCached = (duration: HistoryDuration, tokens: PrefetchedTop export type TopToken = NonNullable[number] interface UseTopTokensReturnValue { + error: Error | undefined loading: boolean tokens: TopToken[] | undefined - tokensWithoutPriceHistoryCount: number hasMore: boolean loadMoreTokens: () => void maxFetchable: number } export function useTopTokens(chain: Chain): UseTopTokensReturnValue { const duration = toHistoryDuration(useAtomValue(filterTimeAtom)) - const [loading, setLoading] = useState(true) + const [loadingTokensWithoutPriceHistory, setLoadingTokensWithoutPriceHistory] = useState(true) + const [loadingTokensWithPriceHistory, setLoadingTokensWithPriceHistory] = useState(true) const [tokens, setTokens] = useState() const [page, setPage] = useState(0) - const prefetchedData = usePrefetchTopTokens(duration, chain) - const prefetchedSelectedTokensWithoutPriceHistory = useFilteredTokens(useSortedTokens(prefetchedData.topTokens)) + const [error, setError] = useState() + const [prefetchedData, setPrefetchedData] = useState([]) + const prefetchedSelectedTokensWithoutPriceHistory = useFilteredTokens(useSortedTokens(prefetchedData)) const maxFetchable = useMemo( () => prefetchedSelectedTokensWithoutPriceHistory.length, [prefetchedSelectedTokensWithoutPriceHistory] ) const hasMore = !tokens || tokens.length < prefetchedSelectedTokensWithoutPriceHistory.length - const environment = useRelayEnvironment() + const loadTokensWithoutPriceHistory = useCallback( + ({ duration, chain }: { duration: HistoryDuration; chain: Chain }) => { + fetchQuery( + environment, + topTokens100Query, + { duration, chain }, + { fetchPolicy: 'store-or-network' } + ).subscribe({ + next: (data) => { + if (data?.topTokens) setPrefetchedData([...data?.topTokens]) + }, + error: setError, + complete: () => setLoadingTokensWithoutPriceHistory(false), + }) + }, + [environment] + ) + // TopTokens should ideally be fetched with usePaginationFragment. The backend does not current support graphql cursors; // in the meantime, fetchQuery is used, as other relay hooks do not allow the refreshing and lazy loading we need const loadTokensWithPriceHistory = useCallback( @@ -208,25 +223,27 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue { tokensQuery, { contracts, duration }, { fetchPolicy: 'store-or-network' } - ) - .toPromise() - .then((data) => { + ).subscribe({ + next: (data) => { if (data?.tokens) { const priceHistoryCacheForCurrentDuration = tokensWithPriceHistoryCache[duration] data.tokens.map((token) => !!token ? (priceHistoryCacheForCurrentDuration[`${token.chain}${token.address}`] = token) : null ) appendingTokens ? setTokens([...(tokens ?? []), ...data.tokens]) : setTokens([...data.tokens]) - setLoading(false) + setLoadingTokensWithPriceHistory(false) setPage(page + 1) } - }) + }, + error: setError, + complete: () => setLoadingTokensWithPriceHistory(false), + }) }, [duration, environment] ) const loadMoreTokens = useCallback(() => { - setLoading(true) + setLoadingTokensWithPriceHistory(true) const contracts = prefetchedSelectedTokensWithoutPriceHistory .slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE) .map(toContractInput) @@ -241,21 +258,27 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue { ) if (everyTokenInCache) { setTokens(cachedTokens) - setLoading(false) - return + setLoadingTokensWithPriceHistory(false) } else { - setLoading(true) + setLoadingTokensWithPriceHistory(true) setTokens([]) const contracts = prefetchedSelectedTokensWithoutPriceHistory.slice(0, PAGE_SIZE).map(toContractInput) loadTokensWithPriceHistory({ contracts, appendingTokens: false, page: 0 }) } }, [loadTokensWithPriceHistory, prefetchedSelectedTokensWithoutPriceHistory, duration]) + // Trigger fetching top 100 tokens without price history on first load, and on + // each change of chain or duration. + useEffect(() => { + setLoadingTokensWithoutPriceHistory(true) + loadTokensWithoutPriceHistory({ duration, chain }) + }, [chain, duration, loadTokensWithoutPriceHistory]) + return { - loading, + error, + loading: loadingTokensWithPriceHistory || loadingTokensWithoutPriceHistory, tokens, hasMore, - tokensWithoutPriceHistoryCount: prefetchedSelectedTokensWithoutPriceHistory.length, loadMoreTokens, maxFetchable, }