feat: use cache while debouncing quotes (#7188)
* feat: check cache before debouncing quote * feat: use cached values if available * fix: initial loading state * fix: no transition to loading * chore: return skipToken from args * test: update snapshots * fix: add back stale state
This commit is contained in:
parent
877e000da6
commit
88b7acf3ae
@ -35,7 +35,8 @@ export const LoadingRows = styled.div`
|
||||
export const loadingOpacityMixin = css<{ $loading: boolean }>`
|
||||
filter: ${({ $loading }) => ($loading ? 'grayscale(1)' : 'none')};
|
||||
opacity: ${({ $loading }) => ($loading ? '0.4' : '1')};
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: ${({ $loading, theme }) =>
|
||||
$loading ? 'none' : `opacity ${theme.transition.duration.medium} ${theme.transition.timing.inOut}`};
|
||||
`
|
||||
|
||||
export const LoadingOpacityContainer = styled.div<{ $loading: boolean }>`
|
||||
|
@ -110,8 +110,8 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
-webkit-transition: opacity 250ms ease-in-out;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
|
@ -70,39 +70,27 @@ export function useDebouncedTrade(
|
||||
[amountSpecified, otherCurrency]
|
||||
)
|
||||
const debouncedSwapQuoteFlagEnabled = useDebounceSwapQuoteFlag() === DebounceSwapQuoteVariant.Enabled
|
||||
const debouncedInputs = useDebounce(inputs, debouncedSwapQuoteFlagEnabled ? DEBOUNCE_TIME_INCREASED : DEBOUNCE_TIME)
|
||||
const isDebouncing = debouncedInputs !== inputs
|
||||
const [debouncedAmount, debouncedOtherCurrency] = debouncedInputs
|
||||
const isDebouncing =
|
||||
useDebounce(inputs, debouncedSwapQuoteFlagEnabled ? DEBOUNCE_TIME_INCREASED : DEBOUNCE_TIME) !== inputs
|
||||
|
||||
const isWrap = useMemo(() => {
|
||||
if (!chainId || !amountSpecified || !debouncedOtherCurrency) return false
|
||||
if (!chainId || !amountSpecified || !otherCurrency) return false
|
||||
const weth = WRAPPED_NATIVE_CURRENCY[chainId]
|
||||
return (
|
||||
(amountSpecified.currency.isNative && weth?.equals(debouncedOtherCurrency)) ||
|
||||
(debouncedOtherCurrency.isNative && weth?.equals(amountSpecified.currency))
|
||||
(amountSpecified.currency.isNative && weth?.equals(otherCurrency)) ||
|
||||
(otherCurrency.isNative && weth?.equals(amountSpecified.currency))
|
||||
)
|
||||
}, [amountSpecified, chainId, debouncedOtherCurrency])
|
||||
}, [amountSpecified, chainId, otherCurrency])
|
||||
|
||||
const shouldGetTrade = !isWrap && isWindowVisible
|
||||
const skipFetch = isDebouncing || !autoRouterSupported || !isWindowVisible || isWrap
|
||||
|
||||
const [routerPreference] = useRouterPreference()
|
||||
const routingAPITrade = useRoutingAPITrade(
|
||||
return useRoutingAPITrade(
|
||||
tradeType,
|
||||
amountSpecified ? debouncedAmount : undefined,
|
||||
debouncedOtherCurrency,
|
||||
amountSpecified,
|
||||
otherCurrency,
|
||||
routerPreferenceOverride ?? routerPreference,
|
||||
!(autoRouterSupported && shouldGetTrade), // skip fetching
|
||||
skipFetch,
|
||||
account
|
||||
)
|
||||
|
||||
// If the user is debouncing, we want to show the loading state until the debounce is complete.
|
||||
const isLoading = (routingAPITrade.state === TradeState.LOADING || isDebouncing) && !isWrap
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...routingAPITrade,
|
||||
...(isLoading ? { state: TradeState.LOADING } : {}),
|
||||
}),
|
||||
[isLoading, routingAPITrade]
|
||||
)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useForceUniswapXOn } from 'featureFlags/flags/forceUniswapXOn'
|
||||
import { useUniswapXEnabled } from 'featureFlags/flags/uniswapx'
|
||||
@ -27,7 +28,7 @@ export function useRoutingAPIArguments({
|
||||
amount?: CurrencyAmount<Currency>
|
||||
tradeType: TradeType
|
||||
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||
}): GetQuoteArgs | undefined {
|
||||
}): GetQuoteArgs | SkipToken {
|
||||
const uniswapXEnabled = useUniswapXEnabled()
|
||||
const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled()
|
||||
const forceUniswapXOn = useForceUniswapXOn()
|
||||
@ -37,7 +38,7 @@ export function useRoutingAPIArguments({
|
||||
return useMemo(
|
||||
() =>
|
||||
!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut) || tokenIn.wrapped.equals(tokenOut.wrapped)
|
||||
? undefined
|
||||
? skipToken
|
||||
: {
|
||||
account,
|
||||
amount: amount.quotient.toString(),
|
||||
|
@ -753,8 +753,8 @@ exports[`disable nft on landing page does not render nft information and card 1`
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
-webkit-transition: opacity 250ms ease-in-out;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
@ -1063,8 +1063,8 @@ exports[`disable nft on landing page does not render nft information and card 1`
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
-webkit-transition: opacity 250ms ease-in-out;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
text-align: left;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
@ -3315,8 +3315,8 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
-webkit-transition: opacity 250ms ease-in-out;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
@ -3625,8 +3625,8 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
-webkit-transition: opacity 250ms ease-in-out;
|
||||
transition: opacity 250ms ease-in-out;
|
||||
text-align: left;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
|
@ -211,3 +211,4 @@ export const routingApi = createApi({
|
||||
})
|
||||
|
||||
export const { useGetQuoteQuery } = routingApi
|
||||
export const useGetQuoteQueryState = routingApi.endpoints.getQuote.useQueryState
|
||||
|
@ -7,7 +7,7 @@ import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments
|
||||
import ms from 'ms'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useGetQuoteQuery } from './slice'
|
||||
import { useGetQuoteQuery, useGetQuoteQueryState } from './slice'
|
||||
import {
|
||||
ClassicTrade,
|
||||
InterfaceTrade,
|
||||
@ -78,55 +78,52 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
||||
account,
|
||||
tokenIn: currencyIn,
|
||||
tokenOut: currencyOut,
|
||||
amount: skipFetch ? undefined : amountSpecified,
|
||||
amount: amountSpecified,
|
||||
tradeType,
|
||||
routerPreference,
|
||||
})
|
||||
|
||||
const {
|
||||
isError,
|
||||
data: tradeResult,
|
||||
error,
|
||||
currentData: currentTradeResult,
|
||||
} = useGetQuoteQuery(queryArgs ?? skipToken, {
|
||||
const { isError, data: tradeResult, error, currentData } = useGetQuoteQueryState(queryArgs)
|
||||
useGetQuoteQuery(skipFetch ? skipToken : queryArgs, {
|
||||
// Price-fetching is informational and costly, so it's done less frequently.
|
||||
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms(`1m`) : AVERAGE_L1_BLOCK_TIME,
|
||||
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
||||
refetchOnMountOrArgChange: 2 * 60,
|
||||
})
|
||||
const isCurrent = currentTradeResult === tradeResult
|
||||
const isFetching = currentData !== tradeResult || !currentData
|
||||
|
||||
return useMemo(() => {
|
||||
if (skipFetch && amountSpecified) {
|
||||
// If we don't want to fetch new trades, but have valid inputs, return the stale trade.
|
||||
return { state: TradeState.STALE, trade: tradeResult?.trade, swapQuoteLatency: tradeResult?.latencyMs }
|
||||
} else if (!amountSpecified || isError || !queryArgs) {
|
||||
if (amountSpecified && queryArgs === skipToken) {
|
||||
return {
|
||||
state: TradeState.STALE,
|
||||
trade: tradeResult?.trade,
|
||||
swapQuoteLatency: tradeResult?.latencyMs,
|
||||
}
|
||||
} else if (!amountSpecified || isError || queryArgs === skipToken) {
|
||||
return {
|
||||
state: TradeState.INVALID,
|
||||
trade: undefined,
|
||||
error: JSON.stringify(error),
|
||||
}
|
||||
} else if (tradeResult?.state === QuoteState.NOT_FOUND && isCurrent) {
|
||||
} else if (tradeResult?.state === QuoteState.NOT_FOUND && !isFetching) {
|
||||
return TRADE_NOT_FOUND
|
||||
} else if (!tradeResult?.trade) {
|
||||
// TODO(WEB-1985): use `isLoading` returned by rtk-query hook instead of checking for `trade` status
|
||||
return TRADE_LOADING
|
||||
} else {
|
||||
return {
|
||||
state: isCurrent ? TradeState.VALID : TradeState.LOADING,
|
||||
trade: tradeResult.trade,
|
||||
swapQuoteLatency: tradeResult.latencyMs,
|
||||
state: isFetching ? TradeState.LOADING : TradeState.VALID,
|
||||
trade: tradeResult?.trade,
|
||||
swapQuoteLatency: tradeResult?.latencyMs,
|
||||
}
|
||||
}
|
||||
}, [
|
||||
amountSpecified,
|
||||
error,
|
||||
isCurrent,
|
||||
isError,
|
||||
isFetching,
|
||||
queryArgs,
|
||||
skipFetch,
|
||||
tradeResult?.state,
|
||||
tradeResult?.latencyMs,
|
||||
tradeResult?.state,
|
||||
tradeResult?.trade,
|
||||
])
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
|
||||
import { useDebouncedTrade } from 'hooks/useDebouncedTrade'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { ParsedQs } from 'qs'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ReactNode, useCallback, useEffect, useMemo } from 'react'
|
||||
import { AnyAction } from 'redux'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
@ -90,7 +90,6 @@ export type SwapInfo = {
|
||||
// from the current swap inputs, compute the best trade and return it.
|
||||
export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefined): SwapInfo {
|
||||
const { account } = useWeb3React()
|
||||
const [previouslyInvalid, setPreviouslyInvalid] = useState(false)
|
||||
|
||||
const {
|
||||
independentField,
|
||||
@ -116,7 +115,7 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine
|
||||
[inputCurrency, isExactIn, outputCurrency, typedValue]
|
||||
)
|
||||
|
||||
let trade = useDebouncedTrade(
|
||||
const trade = useDebouncedTrade(
|
||||
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
parsedAmount,
|
||||
(isExactIn ? outputCurrency : inputCurrency) ?? undefined,
|
||||
@ -124,25 +123,6 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine
|
||||
account
|
||||
)
|
||||
|
||||
const nextPreviouslyInvalid = (() => {
|
||||
if (trade.state === TradeState.INVALID) {
|
||||
return true
|
||||
} else if (trade.state !== TradeState.LOADING) {
|
||||
return false
|
||||
}
|
||||
return undefined
|
||||
})()
|
||||
if (typeof nextPreviouslyInvalid === 'boolean' && nextPreviouslyInvalid !== previouslyInvalid) {
|
||||
setPreviouslyInvalid(nextPreviouslyInvalid)
|
||||
}
|
||||
|
||||
if (trade.state == TradeState.LOADING && previouslyInvalid) {
|
||||
trade = {
|
||||
...trade,
|
||||
trade: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const currencyBalances = useMemo(
|
||||
() => ({
|
||||
[Field.INPUT]: relevantTokenBalances[0],
|
||||
|
Loading…
Reference in New Issue
Block a user