From 2a04f0faca41bed1766e37aea629ceb4e6b6619e Mon Sep 17 00:00:00 2001 From: Tina <59578595+tinaszheng@users.noreply.github.com> Date: Mon, 13 Mar 2023 15:18:20 -0400 Subject: [PATCH] fix: loading states for USD prices (#6141) * set loading state in usdc price fetch * remove console log * use 100 for polygon * use 10k for polygon --- .../CurrencyInputPanel/FiatValue.tsx | 24 ++-------- .../SwapCurrencyInputPanel.tsx | 11 ++--- src/components/CurrencyInputPanel/index.tsx | 2 +- src/components/swap/ConfirmSwapModal.tsx | 4 +- src/components/swap/SwapModalFooter.tsx | 8 ++-- src/components/swap/SwapModalHeader.tsx | 2 +- src/components/swap/TradePrice.tsx | 2 +- src/hooks/useUSDPrice.ts | 47 +++++++++++++------ src/pages/AddLiquidity/index.tsx | 24 ++++++++-- src/pages/Swap/index.tsx | 10 ++-- 10 files changed, 73 insertions(+), 61 deletions(-) diff --git a/src/components/CurrencyInputPanel/FiatValue.tsx b/src/components/CurrencyInputPanel/FiatValue.tsx index 814404b4c5..a4e8df1c2e 100644 --- a/src/components/CurrencyInputPanel/FiatValue.tsx +++ b/src/components/CurrencyInputPanel/FiatValue.tsx @@ -5,7 +5,7 @@ import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/ import { Percent } from '@uniswap/sdk-core' import { LoadingBubble } from 'components/Tokens/loading' import { MouseoverTooltip } from 'components/Tooltip' -import { useEffect, useMemo, useState } from 'react' +import { useMemo } from 'react' import styled, { useTheme } from 'styled-components/macro' import { ThemedText } from '../../theme' @@ -20,14 +20,11 @@ const FiatLoadingBubble = styled(LoadingBubble)` export function FiatValue({ fiatValue, priceImpact, - isLoading = false, }: { - fiatValue: number | null | undefined + fiatValue?: { data?: number; isLoading: boolean } priceImpact?: Percent - isLoading?: boolean }) { const theme = useTheme() - const [showLoadingPlaceholder, setShowLoadingPlaceholder] = useState(false) const priceImpactColor = useMemo(() => { if (!priceImpact) return undefined if (priceImpact.lessThan('0')) return theme.accentSuccess @@ -37,26 +34,13 @@ export function FiatValue({ return theme.accentFailure }, [priceImpact, theme.accentSuccess, theme.accentFailure, theme.textTertiary, theme.deprecated_yellow1]) - useEffect(() => { - const stale = false - let timeoutId = 0 - if (isLoading && !fiatValue) { - timeoutId = setTimeout(() => { - if (!stale) setShowLoadingPlaceholder(true) - }, 200) as unknown as number - } else { - setShowLoadingPlaceholder(false) - } - return () => clearTimeout(timeoutId) - }, [isLoading, fiatValue]) - return ( - {showLoadingPlaceholder ? ( + {fiatValue?.isLoading ? ( ) : (
- {fiatValue ? formatNumber(fiatValue, NumberType.FiatTokenPrice) : undefined} + {fiatValue?.data ? formatNumber(fiatValue.data, NumberType.FiatTokenPrice) : undefined} {priceImpact && ( {' '} diff --git a/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx b/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx index 2f22a5cdf9..7c04d40e3c 100644 --- a/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx +++ b/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx @@ -9,7 +9,7 @@ import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/ import CurrencyLogo from 'components/Logo/CurrencyLogo' import { isSupportedChain } from 'constants/chains' import { darken } from 'polished' -import { ReactNode, useCallback, useEffect, useState } from 'react' +import { ReactNode, useCallback, useState } from 'react' import { Lock } from 'react-feather' import styled, { useTheme } from 'styled-components/macro' import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles' @@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps { pair?: Pair | null hideInput?: boolean otherCurrency?: Currency | null - fiatValue?: number | null + fiatValue: { data?: number; isLoading: boolean } priceImpact?: Percent id: string showCommonBases?: boolean @@ -229,7 +229,6 @@ export default function SwapCurrencyInputPanel({ ...rest }: SwapCurrencyInputPanelProps) { const [modalOpen, setModalOpen] = useState(false) - const [fiatValueIsLoading, setFiatValueIsLoading] = useState(false) const { account, chainId } = useWeb3React() const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) const theme = useTheme() @@ -240,10 +239,6 @@ export default function SwapCurrencyInputPanel({ const chainAllowed = isSupportedChain(chainId) - useEffect(() => { - !!value && !fiatValue ? setFiatValueIsLoading(true) : setFiatValueIsLoading(false) - }, [fiatValueIsLoading, value, fiatValue]) - return ( {locked && ( @@ -311,7 +306,7 @@ export default function SwapCurrencyInputPanel({ - + {account ? ( diff --git a/src/components/CurrencyInputPanel/index.tsx b/src/components/CurrencyInputPanel/index.tsx index 3bfe57862f..9eb7515044 100644 --- a/src/components/CurrencyInputPanel/index.tsx +++ b/src/components/CurrencyInputPanel/index.tsx @@ -182,7 +182,7 @@ interface CurrencyInputPanelProps { pair?: Pair | null hideInput?: boolean otherCurrency?: Currency | null - fiatValue?: number | null + fiatValue?: { data?: number; isLoading: boolean } priceImpact?: Percent id: string showCommonBases?: boolean diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index 8009fa8725..8096c4859f 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -42,8 +42,8 @@ export default function ConfirmSwapModal({ swapErrorMessage: ReactNode | undefined onDismiss: () => void swapQuoteReceivedDate: Date | undefined - fiatValueInput?: number - fiatValueOutput?: number + fiatValueInput: { data?: number; isLoading: boolean } + fiatValueOutput: { data?: number; isLoading: boolean } }) { // shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed // and an event triggered by modal closing should be logged. diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 9a9a2e8d87..44cc726807 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -118,8 +118,8 @@ export default function SwapModalFooter({ swapErrorMessage: ReactNode | undefined disabledConfirm: boolean swapQuoteReceivedDate: Date | undefined - fiatValueInput?: number - fiatValueOutput?: number + fiatValueInput: { data?: number; isLoading: boolean } + fiatValueOutput: { data?: number; isLoading: boolean } }) { const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto' @@ -142,8 +142,8 @@ export default function SwapModalFooter({ isAutoRouterApi: !clientSideRouter, swapQuoteReceivedDate, routes, - fiatValueInput, - fiatValueOutput, + fiatValueInput: fiatValueInput.data, + fiatValueOutput: fiatValueOutput.data, })} > diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx index ab36fbc5df..e3d6ae9fc6 100644 --- a/src/components/swap/TradePrice.tsx +++ b/src/components/swap/TradePrice.tsx @@ -33,7 +33,7 @@ export default function TradePrice({ price }: TradePriceProps) { const [showInverted, setShowInverted] = useState(false) const { baseCurrency, quoteCurrency } = price - const usdPrice = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency)) + const { data: usdPrice } = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency)) let formattedPrice: string try { diff --git a/src/hooks/useUSDPrice.ts b/src/hooks/useUSDPrice.ts index e63e299d1b..a2c875499f 100644 --- a/src/hooks/useUSDPrice.ts +++ b/src/hooks/useUSDPrice.ts @@ -1,8 +1,10 @@ +import { NetworkStatus } from '@apollo/client' import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@uniswap/sdk-core' import { nativeOnChain } from 'constants/tokens' import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util' import { RouterPreference } from 'state/routing/slice' +import { TradeState } from 'state/routing/types' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { getNativeTokenDBAddress } from 'utils/nativeTokens' @@ -11,16 +13,20 @@ import useStablecoinPrice from './useStablecoinPrice' // ETH amounts used when calculating spot price for a given currency. // The amount is large enough to filter low liquidity pairs. const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = { - [SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100), - [SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 100), - [SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 100), - [SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e6), + [SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100e18), + [SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 10e18), + [SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 10e18), + [SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e18), + [SupportedChainId.CELO]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.CELO), 10e18), } -function useETHValue(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined { +function useETHValue(currencyAmount?: CurrencyAmount): { + data: CurrencyAmount | undefined + isLoading: boolean +} { const chainId = currencyAmount?.currency?.chainId const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined - const { trade } = useRoutingAPITrade( + const { trade, state } = useRoutingAPITrade( TradeType.EXACT_OUTPUT, amountOut, currencyAmount?.currency, @@ -29,36 +35,47 @@ function useETHValue(currencyAmount?: CurrencyAmount): CurrencyAmount< // Get ETH value of ETH or WETH if (chainId && currencyAmount && currencyAmount.currency.wrapped.equals(nativeOnChain(chainId).wrapped)) { - return new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount) + return { + data: new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount), + isLoading: false, + } } - if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) return + if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) { + return { data: undefined, isLoading: state === TradeState.LOADING || state === TradeState.SYNCING } + } const { numerator, denominator } = trade.routes[0].midPrice const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator) - return price.quote(currencyAmount) + return { data: price.quote(currencyAmount), isLoading: false } } -export function useUSDPrice(currencyAmount?: CurrencyAmount): number | undefined { +export function useUSDPrice(currencyAmount?: CurrencyAmount): { + data: number | undefined + isLoading: boolean +} { const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined const currency = currencyAmount?.currency - const ethValue = useETHValue(currencyAmount) + const { data: ethValue, isLoading: isEthValueLoading } = useETHValue(currencyAmount) - const { data } = useTokenSpotPriceQuery({ + const { data, networkStatus } = useTokenSpotPriceQuery({ variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) }, skip: !chain || !isGqlSupportedChain(currency?.chainId), pollInterval: PollingInterval.Normal, + notifyOnNetworkStatusChange: true, }) // Use USDC price for chains not supported by backend yet const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined) if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) { - return parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()) + return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false } } + const isFirstLoad = networkStatus === NetworkStatus.loading + // Otherwise, get the price of the token in ETH, and then multiple by the price of ETH const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value - if (!ethUSDPrice || !ethValue) return undefined + if (!ethUSDPrice || !ethValue) return { data: undefined, isLoading: isEthValueLoading || isFirstLoad } - return parseFloat(ethValue.toExact()) * ethUSDPrice + return { data: parseFloat(ethValue.toExact()) * ethUSDPrice, isLoading: false } } diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx index 23e8ee44cb..9e84fe5704 100644 --- a/src/pages/AddLiquidity/index.tsx +++ b/src/pages/AddLiquidity/index.tsx @@ -9,7 +9,7 @@ import { useWeb3React } from '@web3-react/core' import { sendEvent } from 'components/analytics' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import usePrevious from 'hooks/usePrevious' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { AlertTriangle } from 'react-feather' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { Text } from 'rebass' @@ -532,8 +532,22 @@ export default function AddLiquidity() { ) - const currencyAFiat = usdcValues[Field.CURRENCY_A] - const currencyBFiat = usdcValues[Field.CURRENCY_B] + const usdcValueCurrencyA = usdcValues[Field.CURRENCY_A] + const usdcValueCurrencyB = usdcValues[Field.CURRENCY_B] + const currencyAFiat = useMemo( + () => ({ + data: usdcValueCurrencyA ? parseFloat(usdcValueCurrencyA.toSignificant()) : undefined, + isLoading: false, + }), + [usdcValueCurrencyA] + ) + const currencyBFiat = useMemo( + () => ({ + data: usdcValueCurrencyB ? parseFloat(usdcValueCurrencyB.toSignificant()) : undefined, + isLoading: false, + }), + [usdcValueCurrencyB] + ) return ( <> @@ -684,7 +698,7 @@ export default function AddLiquidity() { showMaxButton={!atMaxAmounts[Field.CURRENCY_A]} currency={currencies[Field.CURRENCY_A] ?? null} id="add-liquidity-input-tokena" - fiatValue={currencyAFiat && parseFloat(currencyAFiat.toSignificant())} + fiatValue={currencyAFiat} showCommonBases locked={depositADisabled} /> @@ -696,7 +710,7 @@ export default function AddLiquidity() { onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '') }} showMaxButton={!atMaxAmounts[Field.CURRENCY_B]} - fiatValue={currencyBFiat && parseFloat(currencyBFiat.toSignificant())} + fiatValue={currencyBFiat} currency={currencies[Field.CURRENCY_B] ?? null} id="add-liquidity-input-tokenb" showCommonBases diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index ac0e7d785d..6bc8fca6e1 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -242,7 +242,9 @@ export default function Swap({ className }: { className?: string }) { const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount) const stablecoinPriceImpact = useMemo( () => - routeIsSyncing || !trade ? undefined : computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput), + routeIsSyncing || !trade + ? undefined + : computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data), [fiatValueTradeInput, fiatValueTradeOutput, routeIsSyncing, trade] ) @@ -334,7 +336,7 @@ export default function Swap({ className }: { className?: string }) { ) const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) const swapFiatValues = useMemo(() => { - return { amountIn: fiatValueTradeInput, amountOut: fiatValueTradeOutput } + return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data } }, [fiatValueTradeInput, fiatValueTradeOutput]) // the callback to execute the swap @@ -534,7 +536,7 @@ export default function Swap({ className }: { className?: string }) { currency={currencies[Field.INPUT] ?? null} onUserInput={handleTypeInput} onMax={handleMaxInput} - fiatValue={fiatValueInput ?? undefined} + fiatValue={fiatValueInput} onCurrencySelect={handleInputSelect} otherCurrency={currencies[Field.OUTPUT]} showCommonBases={true} @@ -581,7 +583,7 @@ export default function Swap({ className }: { className?: string }) { } showMaxButton={false} hideBalance={false} - fiatValue={fiatValueOutput ?? undefined} + fiatValue={fiatValueOutput} priceImpact={stablecoinPriceImpact} currency={currencies[Field.OUTPUT] ?? null} onCurrencySelect={handleOutputSelect}