Commit 963b9105 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: trade loading state (#3572)

* fix: invert stale callback

* fix: polling and validation logic

* fix: rm unused conditional
parent 9e2dc9a4
......@@ -84,11 +84,11 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
const getIsValidBlock = useGetIsValidBlock()
const { data: quoteResult, error } = usePoll(getQuoteResult, JSON.stringify(queryArgs), {
debounce: isDebouncing,
staleCallback: useCallback(({ data }) => getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
staleCallback: useCallback(({ data }) => !getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
}) ?? {
error: undefined,
}
const isLoading = !quoteResult
const isValid = getIsValidBlock(Number(quoteResult?.blockNumber) || 0)
const route = useMemo(
() => computeRoutes(currencyIn, currencyOut, tradeType, quoteResult),
......@@ -118,14 +118,14 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
}
// Returns the last trade state while syncing/loading to avoid jank from clearing the last trade while loading.
if (!quoteResult && !error) {
if (!trade && !error) {
if (isStale) {
return { state: TradeState.LOADING, trade: undefined }
} else if (isDebouncing) {
return { state: TradeState.SYNCING, trade: lastTrade }
} else if (isLoading) {
return { state: TradeState.LOADING, trade: lastTrade }
}
} else if (!isValid && !error) {
return { state: TradeState.LOADING, trade: lastTrade }
}
let otherAmount = undefined
......@@ -151,14 +151,14 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
}, [
currencyIn,
currencyOut,
quoteResult,
trade,
error,
isValid,
quoteResult,
route,
queryArgs,
trade,
isStale,
isDebouncing,
isLoading,
lastTrade,
tradeType,
])
......
......@@ -8,13 +8,18 @@ interface PollingOptions<T> {
// If true, any cached result will be returned, but no new fetch will be initiated.
debounce?: boolean
// If stale, a result will not be returned, and a new fetch will be immediately initiated.
// If stale, any cached result will be returned, and a new fetch will be initiated.
staleCallback?: (value: T) => boolean
pollingInterval?: number
keepUnusedDataFor?: number
}
interface CacheEntry<T> {
ttl: number | null // null denotes a pending fetch
result?: T
}
export default function usePoll<T>(
fetch: () => Promise<T>,
key = '',
......@@ -25,7 +30,7 @@ export default function usePoll<T>(
keepUnusedDataFor = DEFAULT_KEEP_UNUSED_DATA_FOR,
}: PollingOptions<T>
): T | undefined {
const cache = useMemo(() => new Map<string, { ttl: number; result?: T }>(), [])
const cache = useMemo(() => new Map<string, CacheEntry<T>>(), [])
const [, setData] = useState<{ key: string; result?: T }>({ key })
useEffect(() => {
......@@ -35,29 +40,33 @@ export default function usePoll<T>(
const entry = cache.get(key)
const isStale = staleCallback && entry?.result !== undefined ? staleCallback(entry.result) : false
if (entry && entry.ttl + keepUnusedDataFor > Date.now() && !isStale) {
// If there is a fresh entry, return it and queue the next poll.
setData({ key, result: entry.result })
timeout = setTimeout(poll, Math.max(0, entry.ttl - Date.now()))
if (entry) {
// If there is not a pending fetch (and there should be), queue one.
if (entry.ttl) {
if (isStale) {
poll() // stale results should be refetched immediately
} else if (entry.ttl && entry.ttl + keepUnusedDataFor > Date.now()) {
timeout = setTimeout(poll, Math.max(0, entry.ttl - Date.now()))
}
}
} else {
// Otherwise, set a new entry (to avoid duplicate polling) and trigger a poll immediately.
cache.set(key, { ttl: Date.now() + pollingInterval })
setData({ key })
// If there is no cached entry, trigger a poll immediately.
poll()
}
setData({ key, result: entry?.result })
return () => {
clearTimeout(timeout)
}
async function poll(ttl = Date.now() + pollingInterval) {
timeout = setTimeout(poll, pollingInterval)
const result = await fetch()
timeout = setTimeout(poll, pollingInterval) // queue the next poll
cache.set(key, { ttl: null, ...cache.get(key) }) // mark the entry as a pending fetch
// Always set the result in the cache, but only set it as data if the key is still being queried.
const result = await fetch()
cache.set(key, { ttl, result })
setData((data) => {
return data.key === key ? { key, result } : data
})
setData((data) => (data.key === key ? { key, result } : data))
}
}, [cache, debounce, fetch, keepUnusedDataFor, key, pollingInterval, staleCallback])
......@@ -67,7 +76,7 @@ export default function usePoll<T>(
const now = Date.now()
cache.forEach(({ ttl }, key) => {
if (ttl + keepUnusedDataFor <= now) {
if (ttl && ttl + keepUnusedDataFor <= now) {
cache.delete(key)
}
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment