fix: glitchy lazy loading (big jump / unnecessary scroll) (#4776)
* fix glitchy loading * fix initial no tokens state
This commit is contained in:
parent
9859c0b4dd
commit
8c1e41a3a8
@ -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<IntersectionObserver>()
|
||||
@ -93,9 +92,9 @@ export default function TokenTable() {
|
||||
|
||||
/* loading and error state */
|
||||
if (loading && (!tokens || tokens?.length === 0)) {
|
||||
return <LoadingTokenTable rowCount={Math.min(tokensWithoutPriceHistoryCount, PAGE_SIZE)} />
|
||||
return <LoadingTokenTable rowCount={PAGE_SIZE} />
|
||||
} else {
|
||||
if (!tokens) {
|
||||
if (error || !tokens) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
|
@ -8,8 +8,8 @@ import {
|
||||
sortMethodAtom,
|
||||
} from 'components/Tokens/state'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
|
||||
import { fetchQuery, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
||||
import { fetchQuery, useRelayEnvironment } from 'react-relay'
|
||||
|
||||
import {
|
||||
Chain,
|
||||
@ -20,10 +20,6 @@ import {
|
||||
import type { TopTokens100Query } from './__generated__/TopTokens100Query.graphql'
|
||||
import { toHistoryDuration } from './util'
|
||||
|
||||
export function usePrefetchTopTokens(duration: HistoryDuration, chain: Chain) {
|
||||
return useLazyLoadQuery<TopTokens100Query>(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<TopTokens_TokensQuery['response']['tokens']>[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<TopToken[]>()
|
||||
const [page, setPage] = useState(0)
|
||||
const prefetchedData = usePrefetchTopTokens(duration, chain)
|
||||
const prefetchedSelectedTokensWithoutPriceHistory = useFilteredTokens(useSortedTokens(prefetchedData.topTokens))
|
||||
const [error, setError] = useState<Error | undefined>()
|
||||
const [prefetchedData, setPrefetchedData] = useState<PrefetchedTopToken[]>([])
|
||||
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<TopTokens100Query>(
|
||||
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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user