Commit e480f0eb authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

chore: simplify swap info (#3710)

* fix: prevent unnecessary TokenImg renders

* fix: prevent unnecessary trade renders

* fix: simplify swap info computation
parent f6ceecbc
...@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains' ...@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { useSwapInfo } from 'lib/hooks/swap' import { useSwapInfo } from 'lib/hooks/swap'
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo' import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
import { Field, swapAtom } from 'lib/state/swap' import { Field, swapAtom } from 'lib/state/swap'
import { useEffect } from 'react' import { useEffect } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
...@@ -54,7 +54,8 @@ function Fixture() { ...@@ -54,7 +54,8 @@ function Fixture() {
export default ( export default (
<> <>
<SwapInfoUpdater /> <SwapInfoProvider>
<Fixture /> <Fixture />
</SwapInfoProvider>
</> </>
) )
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { TokenInfo } from '@uniswap/token-lists' import { TokenInfo } from '@uniswap/token-lists'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo' import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
import useSyncConvenienceFee, { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee' import useSyncConvenienceFee, { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
import useSyncTokenDefaults, { TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults' import useSyncTokenDefaults, { TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
import { usePendingTransactions } from 'lib/hooks/transactions' import { usePendingTransactions } from 'lib/hooks/transactions'
...@@ -50,8 +50,7 @@ export interface SwapProps extends TokenDefaults, FeeOptions { ...@@ -50,8 +50,7 @@ export interface SwapProps extends TokenDefaults, FeeOptions {
function Updaters(props: SwapProps) { function Updaters(props: SwapProps) {
useSyncTokenDefaults(props) useSyncTokenDefaults(props)
useSyncConvenienceFee(props) useSyncConvenienceFee(props)
return null
return <SwapInfoUpdater />
} }
export default function Swap(props: SwapProps) { export default function Swap(props: SwapProps) {
...@@ -68,25 +67,27 @@ export default function Swap(props: SwapProps) { ...@@ -68,25 +67,27 @@ export default function Swap(props: SwapProps) {
const isDisabled = !(active && onSupportedNetwork) const isDisabled = !(active && onSupportedNetwork)
useSyncTokenList(props.tokenList) useSyncTokenList(props.tokenList)
const isUpdateable = useIsTokenListLoaded() && !isDisabled const isTokenListLoaded = useIsTokenListLoaded()
const focused = useHasFocus(wrapper) const focused = useHasFocus(wrapper)
return ( return (
<> <>
{isUpdateable && <Updaters {...props} />} {isTokenListLoaded && <Updaters {...props} />}
<Header title={<Trans>Swap</Trans>}> <Header title={<Trans>Swap</Trans>}>
{active && <Wallet disabled={!account} onClick={props.onConnectWallet} />} {active && <Wallet disabled={!account} onClick={props.onConnectWallet} />}
<Settings disabled={isDisabled} /> <Settings disabled={isDisabled} />
</Header> </Header>
<div ref={setWrapper}> <div ref={setWrapper}>
<BoundaryProvider value={wrapper}> <BoundaryProvider value={wrapper}>
<Input disabled={isDisabled} focused={focused} /> <SwapInfoProvider disabled={isDisabled}>
<ReverseButton disabled={isDisabled} /> <Input disabled={isDisabled} focused={focused} />
<Output disabled={isDisabled} focused={focused}> <ReverseButton disabled={isDisabled} />
<Toolbar disabled={!active} /> <Output disabled={isDisabled} focused={focused}>
<SwapButton disabled={isDisabled} /> <Toolbar disabled={!active} />
</Output> <SwapButton disabled={isDisabled} />
</Output>
</SwapInfoProvider>
</BoundaryProvider> </BoundaryProvider>
</div> </div>
{displayTx && ( {displayTx && (
......
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { atom } from 'jotai' import { useAtomValue } from 'jotai/utils'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import useSlippage, { DEFAULT_SLIPPAGE, Slippage } from 'lib/hooks/useSlippage' import useSlippage, { DEFAULT_SLIPPAGE, Slippage } from 'lib/hooks/useSlippage'
import useUSDCPriceImpact, { PriceImpact } from 'lib/hooks/useUSDCPriceImpact' import useUSDCPriceImpact, { PriceImpact } from 'lib/hooks/useUSDCPriceImpact'
import { Field, swapAtom } from 'lib/state/swap' import { Field, swapAtom } from 'lib/state/swap'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useEffect, useMemo } from 'react' import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import { INVALID_TRADE, useBestTrade } from './useBestTrade' import { INVALID_TRADE, useBestTrade } from './useBestTrade'
...@@ -58,6 +57,12 @@ function useComputeSwapInfo(): SwapInfo { ...@@ -58,6 +57,12 @@ function useComputeSwapInfo(): SwapInfo {
[isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount] [isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount]
) )
const { account } = useActiveWeb3React()
const [balanceIn, balanceOut] = useCurrencyBalances(
account,
useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut])
)
// Compute slippage and impact off of the trade so that it refreshes with the trade. // Compute slippage and impact off of the trade so that it refreshes with the trade.
// (Using amountIn/amountOut would show (incorrect) intermediate values.) // (Using amountIn/amountOut would show (incorrect) intermediate values.)
const slippage = useSlippage(trade.trade) const slippage = useSlippage(trade.trade)
...@@ -66,73 +71,55 @@ function useComputeSwapInfo(): SwapInfo { ...@@ -66,73 +71,55 @@ function useComputeSwapInfo(): SwapInfo {
return useMemo( return useMemo(
() => ({ () => ({
[Field.INPUT]: { [Field.INPUT]: {
currency: currencyIn,
amount: amountIn, amount: amountIn,
balance: balanceIn,
usdc: inputUSDC, usdc: inputUSDC,
}, },
[Field.OUTPUT]: { [Field.OUTPUT]: {
currency: currencyOut,
amount: amountOut, amount: amountOut,
balance: balanceOut,
usdc: outputUSDC, usdc: outputUSDC,
}, },
trade, trade,
slippage, slippage,
impact, impact,
}), }),
[amountIn, amountOut, impact, inputUSDC, outputUSDC, slippage, trade] [
amountIn,
amountOut,
balanceIn,
balanceOut,
currencyIn,
currencyOut,
impact,
inputUSDC,
outputUSDC,
slippage,
trade,
]
) )
} }
const swapInfoAtom = atom<SwapInfo>({ const DEFAULT_SWAP_INFO: SwapInfo = {
[Field.INPUT]: {}, [Field.INPUT]: {},
[Field.OUTPUT]: {}, [Field.OUTPUT]: {},
trade: INVALID_TRADE, trade: INVALID_TRADE,
slippage: DEFAULT_SLIPPAGE, slippage: DEFAULT_SLIPPAGE,
}) }
const SwapInfoContext = createContext(DEFAULT_SWAP_INFO)
export function SwapInfoUpdater() { export function SwapInfoProvider({ children, disabled }: PropsWithChildren<{ disabled?: boolean }>) {
const setSwapInfo = useUpdateAtom(swapInfoAtom)
const swapInfo = useComputeSwapInfo() const swapInfo = useComputeSwapInfo()
useEffect(() => setSwapInfo(swapInfo), [setSwapInfo, swapInfo]) if (disabled) {
return null return <SwapInfoContext.Provider value={DEFAULT_SWAP_INFO}>{children}</SwapInfoContext.Provider>
}
return <SwapInfoContext.Provider value={swapInfo}>{children}</SwapInfoContext.Provider>
} }
/** Requires that SwapInfoUpdater be installed in the DOM tree. **/ /** Requires that SwapInfoUpdater be installed in the DOM tree. **/
export default function useSwapInfo(): SwapInfo { export default function useSwapInfo(): SwapInfo {
const swapInfo = useAtomValue(swapInfoAtom) return useContext(SwapInfoContext)
const { [Field.INPUT]: currencyIn, [Field.OUTPUT]: currencyOut } = useAtomValue(swapAtom)
const trade = useMemo(() => {
const trade = swapInfo.trade
if (trade.state === TradeState.VALID && trade.trade) {
if (
(currencyIn && !trade.trade.inputAmount.currency.equals(currencyIn)) ||
(currencyOut && !trade.trade.outputAmount.currency.equals(currencyOut))
) {
// swapInfo has not yet caught up to swapAtom.
return { ...trade, state: TradeState.LOADING }
}
}
return trade
}, [currencyIn, currencyOut, swapInfo.trade])
const { account } = useActiveWeb3React()
const [balanceIn, balanceOut] = useCurrencyBalances(
account,
useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut])
)
// swapInfo will lag behind swapAtom by a frame, because its update is triggered by swapAtom
// so a swap must be marked as loading, with up-to-date currencies, during that update.
// In other words, swapInfo is derived from swapAtom, so it must be used as the source of truth.
const input = useMemo(
() => ({ ...swapInfo[Field.INPUT], currency: currencyIn, balance: balanceIn }),
[balanceIn, currencyIn, swapInfo]
)
const output = useMemo(
() => ({ ...swapInfo[Field.OUTPUT], currency: currencyOut, balance: balanceOut }),
[balanceOut, currencyOut, swapInfo]
)
return useMemo(
() => ({ ...swapInfo, trade, [Field.INPUT]: input, [Field.OUTPUT]: output }),
[input, output, swapInfo, trade]
)
} }
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