Commit 04ded04e authored by Lynn's avatar Lynn Committed by GitHub

fix: add animated loading bars and ensure sorted tokens upon load (#3874)

* fix: add animated loading bars and ensure sorted tokens upon load

* refactor: undo refactor of token selector component in CurrencySearch

* fix: fix styling as per design, still need to respond to other comments in review

* fix: add timeout to token loader of 2 seconds

* fix: add snapshot test and styling changes as per fred rec

* refactor: simplify function in currency list test

* fix: increase loading bars time from 2 seconds to 3 sec

* fix: respond to zach's comments

* fix: fix import errors
Co-authored-by: default avatarLynn Yu <lynn.yu@UNISWAP-MAC-015.local>
parent cf188a86
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
<DocumentFragment>
<div
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 168px; width: 100%;"
>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
>
CurrencyLogo currency=DAI
<div
class="Column-sc-1r2yyln-0 cYEAJI"
>
<div
class="css-8mokm4"
title="Dai Stablecoin"
>
DAI
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
>
Dai Stablecoin
</div>
</div>
<span />
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
>
CurrencyLogo currency=USDC
<div
class="Column-sc-1r2yyln-0 cYEAJI"
>
<div
class="css-8mokm4"
title="USD//C"
>
USDC
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
>
USD//C
</div>
</div>
<span />
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__RowBetween-sc-u7azg8-1 styleds__MenuItem-sc-muzgnq-3 lmTMKd hLLNig hzJkYd firMKT token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
>
CurrencyLogo currency=WBTC
<div
class="Column-sc-1r2yyln-0 cYEAJI"
>
<div
class="css-8mokm4"
title="Wrapped BTC"
>
WBTC
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 gVIOIC css-165qfk5"
>
Wrapped BTC
</div>
</div>
<span />
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`renders loading rows when isLoading is true 1`] = `
<DocumentFragment>
<div
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 0px; width: 100%;"
/>
</div>
</DocumentFragment>
`;
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import * as mockJSBI from 'jsbi'
import { render } from 'test-utils'
import CurrencyList from '.'
const noOp = function () {
// do nothing
}
const mockCurrencyAmt = {
[DAI.address]: mockCurrencyAmount.fromRawAmount(DAI, mockJSBI.default.BigInt(100)),
[USDC_MAINNET.address]: mockCurrencyAmount.fromRawAmount(USDC_MAINNET, mockJSBI.default.BigInt(10)),
[WBTC.address]: mockCurrencyAmount.fromRawAmount(WBTC, mockJSBI.default.BigInt(1)),
}
jest.mock(
'components/CurrencyLogo',
() =>
({ currency }: { currency: Currency }) =>
`CurrencyLogo currency=${currency.symbol}`
)
jest.mock('hooks/useActiveWeb3React', () => {
return {
__esModule: true,
default: () => ({
account: '123',
active: true,
}),
}
})
jest.mock('../../../state/wallet/hooks', () => {
return {
useCurrencyBalance: (currency: Currency) => {
return mockCurrencyAmt[(currency as mockToken).address]
},
}
})
it('renders loading rows when isLoading is true', () => {
const { asFragment } = render(
<CurrencyList
height={10}
currencies={[]}
otherListTokens={[]}
selectedCurrency={null}
onCurrencySelect={noOp}
showImportView={noOp}
setImportToken={noOp}
isLoading={true}
/>
)
expect(asFragment()).toMatchSnapshot()
})
it('renders currency rows correctly when currencies list is non-empty', () => {
const { asFragment } = render(
<CurrencyList
height={10}
currencies={[DAI, USDC_MAINNET, WBTC]}
otherListTokens={[]}
selectedCurrency={null}
onCurrencySelect={noOp}
showImportView={noOp}
setImportToken={noOp}
isLoading={false}
/>
)
expect(asFragment()).toMatchSnapshot()
})
...@@ -9,20 +9,20 @@ import { FixedSizeList } from 'react-window' ...@@ -9,20 +9,20 @@ import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import TokenListLogo from '../../assets/svg/tokenlist.svg' import TokenListLogo from '../../../assets/svg/tokenlist.svg'
import { useIsUserAddedToken } from '../../hooks/Tokens' import { useIsUserAddedToken } from '../../../hooks/Tokens'
import { useCombinedActiveList } from '../../state/lists/hooks' import { useCombinedActiveList } from '../../../state/lists/hooks'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
import { useCurrencyBalance } from '../../state/wallet/hooks' import { useCurrencyBalance } from '../../../state/wallet/hooks'
import { ThemedText } from '../../theme' import { ThemedText } from '../../../theme'
import { isTokenOnList } from '../../utils' import { isTokenOnList } from '../../../utils'
import Column from '../Column' import Column from '../../Column'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../../CurrencyLogo'
import Loader from '../Loader' import Loader from '../../Loader'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../../Row'
import { MouseoverTooltip } from '../Tooltip' import { MouseoverTooltip } from '../../Tooltip'
import ImportRow from './ImportRow' import ImportRow from '../ImportRow'
import { MenuItem } from './styleds' import { LoadingRows, MenuItem } from '../styleds'
function currencyKey(currency: Currency): string { function currencyKey(currency: Currency): string {
return currency.isToken ? currency.address : 'ETHER' return currency.isToken ? currency.address : 'ETHER'
...@@ -195,6 +195,7 @@ export default function CurrencyList({ ...@@ -195,6 +195,7 @@ export default function CurrencyList({
showImportView, showImportView,
setImportToken, setImportToken,
showCurrencyAmount, showCurrencyAmount,
isLoading,
}: { }: {
height: number height: number
currencies: Currency[] currencies: Currency[]
...@@ -206,6 +207,7 @@ export default function CurrencyList({ ...@@ -206,6 +207,7 @@ export default function CurrencyList({
showImportView: () => void showImportView: () => void
setImportToken: (token: Token) => void setImportToken: (token: Token) => void
showCurrencyAmount?: boolean showCurrencyAmount?: boolean
isLoading: boolean
}) { }) {
const itemData: (Currency | BreakLine)[] = useMemo(() => { const itemData: (Currency | BreakLine)[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) { if (otherListTokens && otherListTokens?.length > 0) {
...@@ -232,7 +234,15 @@ export default function CurrencyList({ ...@@ -232,7 +234,15 @@ export default function CurrencyList({
const showImport = index > currencies.length const showImport = index > currencies.length
if (showImport && token) { if (isLoading) {
return (
<LoadingRows>
<div />
<div />
<div />
</LoadingRows>
)
} else if (showImport && token) {
return ( return (
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim /> <ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
) )
...@@ -259,6 +269,7 @@ export default function CurrencyList({ ...@@ -259,6 +269,7 @@ export default function CurrencyList({
setImportToken, setImportToken,
showImportView, showImportView,
showCurrencyAmount, showCurrencyAmount,
isLoading,
] ]
) )
......
...@@ -74,6 +74,8 @@ export function CurrencySearch({ ...@@ -74,6 +74,8 @@ export function CurrencySearch({
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const theme = useTheme() const theme = useTheme()
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
// refs for fixed size lists // refs for fixed size lists
const fixedList = useRef<FixedSizeList>() const fixedList = useRef<FixedSizeList>()
...@@ -103,10 +105,11 @@ export function CurrencySearch({ ...@@ -103,10 +105,11 @@ export function CurrencySearch({
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery)) return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
}, [allTokens, debouncedQuery]) }, [allTokens, debouncedQuery])
const balances = useAllTokenBalances() const [balances, balancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(() => { const sortedTokens: Token[] = useMemo(() => {
return filteredTokens.sort(tokenComparator.bind(null, balances)) void balancesIsLoading // creates a new array once balances load to update hooks
}, [balances, filteredTokens]) return [...filteredTokens].sort(tokenComparator.bind(null, balances))
}, [balances, filteredTokens, balancesIsLoading])
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens) const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
...@@ -173,6 +176,14 @@ export function CurrencySearch({ ...@@ -173,6 +176,14 @@ export function CurrencySearch({
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
) )
// Timeout token loader after 3 seconds to avoid hanging in a loading state.
useEffect(() => {
const tokenLoaderTimer = setTimeout(() => {
setTokenLoaderTimerElapsed(true)
}, 3000)
return () => clearTimeout(tokenLoaderTimer)
}, [])
return ( return (
<ContentWrapper> <ContentWrapper>
<PaddedColumn gap="16px"> <PaddedColumn gap="16px">
...@@ -218,6 +229,7 @@ export function CurrencySearch({ ...@@ -218,6 +229,7 @@ export function CurrencySearch({
showImportView={showImportView} showImportView={showImportView}
setImportToken={setImportToken} setImportToken={setImportToken}
showCurrencyAmount={showCurrencyAmount} showCurrencyAmount={showCurrencyAmount}
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
/> />
)} )}
</AutoSizer> </AutoSizer>
......
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
...@@ -72,3 +73,25 @@ export const SeparatorDark = styled.div` ...@@ -72,3 +73,25 @@ export const SeparatorDark = styled.div`
height: 1px; height: 1px;
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme }) => theme.bg3};
` `
export const LoadingRows = styled(BaseLoadingRows)`
grid-column-gap: 0.5em;
grid-template-columns: repeat(12, 1fr);
max-width: 960px;
padding: 12px 20px;
& > div:nth-child(4n + 1) {
grid-column: 1 / 8;
height: 1em;
margin-bottom: 0.25em;
}
& > div:nth-child(4n + 2) {
grid-column: 12;
height: 1em;
margin-top: 0.25em;
}
& > div:nth-child(4n + 3) {
grid-column: 1 / 4;
height: 0.75em;
}
`
import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { useTokenBalance, useTokenBalances } from 'lib/hooks/useCurrencyBalance' import { useTokenBalance, useTokenBalancesWithLoadingIndicator } from 'lib/hooks/useCurrencyBalance'
import { useMemo } from 'react' import { useMemo } from 'react'
import { UNI } from '../../constants/tokens' import { UNI } from '../../constants/tokens'
...@@ -19,12 +19,12 @@ export { ...@@ -19,12 +19,12 @@ export {
} from 'lib/hooks/useCurrencyBalance' } from 'lib/hooks/useCurrencyBalance'
// mimics useAllBalances // mimics useAllBalances
export function useAllTokenBalances(): { [tokenAddress: string]: CurrencyAmount<Token> | undefined } { export function useAllTokenBalances(): [{ [tokenAddress: string]: CurrencyAmount<Token> | undefined }, boolean] {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const allTokens = useAllTokens() const allTokens = useAllTokens()
const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens]) const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens])
const balances = useTokenBalances(account ?? undefined, allTokensArray) const [balances, balancesIsLoading] = useTokenBalancesWithLoadingIndicator(account ?? undefined, allTokensArray)
return balances ?? {} return [balances ?? {}, balancesIsLoading]
} }
// get the total owned, unclaimed, and unharvested UNI for account // get the total owned, unclaimed, and unharvested UNI for account
......
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