diff --git a/src/components/SearchModal/CurrencySearch.tsx b/src/components/SearchModal/CurrencySearch.tsx index be08e73fc2..1439029c38 100644 --- a/src/components/SearchModal/CurrencySearch.tsx +++ b/src/components/SearchModal/CurrencySearch.tsx @@ -17,8 +17,9 @@ import { FixedSizeList } from 'react-window' import { Text } from 'rebass' import { useAllTokenBalances } from 'state/connection/hooks' import styled, { useTheme } from 'styled-components/macro' +import { UserAddedToken } from 'types/tokens' -import { useActiveTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens' +import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens' import { CloseIcon, ThemedText } from '../../theme' import { isAddress } from '../../utils' import Column from '../Column' @@ -66,15 +67,8 @@ export function CurrencySearch({ const [searchQuery, setSearchQuery] = useState('') const debouncedQuery = useDebounce(searchQuery, 200) - - // Only display 'imported' tokens when the search filter has input - const defaultTokens = useActiveTokens(debouncedQuery.length > 0) - - // if they input an address, use it const isAddressSearch = isAddress(debouncedQuery) - const searchToken = useToken(debouncedQuery) - const searchTokenIsAdded = useIsUserAddedToken(searchToken) useEffect(() => { @@ -87,13 +81,26 @@ export function CurrencySearch({ } }, [isAddressSearch]) + const defaultTokens = useAllTokens() const filteredTokens: Token[] = useMemo(() => { return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery)) }, [defaultTokens, debouncedQuery]) const [balances, balancesAreLoading] = useAllTokenBalances() const sortedTokens: Token[] = useMemo( - () => (!balancesAreLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []), + () => + !balancesAreLoading + ? [...filteredTokens] + .filter((token) => { + // Filter out user-added tokens with no balance + if (token instanceof UserAddedToken) { + const balance = balances[token.address] + return balance?.greaterThan(0) + } + return true + }) + .sort(tokenComparator.bind(null, balances)) + : [], [balances, filteredTokens, balancesAreLoading] ) const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed) diff --git a/src/hooks/Tokens.ts b/src/hooks/Tokens.ts index b92bb3f3d6..efee00d4ae 100644 --- a/src/hooks/Tokens.ts +++ b/src/hooks/Tokens.ts @@ -14,50 +14,38 @@ import { useUserAddedTokens, useUserAddedTokensOnChain } from '../state/user/hoo import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks' // reduce token map into standard address <-> Token mapping, optionally include user added tokens -function useTokensFromMap(tokenMap: TokenAddressMap, includeUserAdded: boolean): { [address: string]: Token } { +function useTokensFromMap(tokenMap: TokenAddressMap): { [address: string]: Token } { const { chainId } = useWeb3React() - const userAddedTokens = useUserAddedTokens() - return useMemo(() => { if (!chainId) return {} // reduce to just tokens - const mapWithoutUrls = Object.keys(tokenMap[chainId] ?? {}).reduce<{ [address: string]: Token }>( - (newMap, address) => { - newMap[address] = tokenMap[chainId][address].token - return newMap - }, - {} - ) - - if (includeUserAdded) { - return ( - userAddedTokens - // reduce into all ALL_TOKENS filtered by the current chain - .reduce<{ [address: string]: Token }>( - (tokenMap, token) => { - tokenMap[token.address] = token - return tokenMap - }, - // must make a copy because reduce modifies the map, and we do not - // want to make a copy in every iteration - { ...mapWithoutUrls } - ) - ) - } - - return mapWithoutUrls - }, [chainId, userAddedTokens, tokenMap, includeUserAdded]) + return Object.keys(tokenMap[chainId] ?? {}).reduce<{ [address: string]: Token }>((newMap, address) => { + newMap[address] = tokenMap[chainId][address].token + return newMap + }, {}) + }, [chainId, tokenMap]) } export function useAllTokens(): { [address: string]: Token } { const allTokens = useCombinedActiveList() - return useTokensFromMap(allTokens, true) -} - -export function useActiveTokens(includeUserAdded: boolean): { [address: string]: Token } { - const allTokens = useCombinedActiveList() - return useTokensFromMap(allTokens, includeUserAdded) + const tokensFromMap = useTokensFromMap(allTokens) + const userAddedTokens = useUserAddedTokens() + return useMemo(() => { + return ( + userAddedTokens + // reduce into all ALL_TOKENS filtered by the current chain + .reduce<{ [address: string]: Token }>( + (tokenMap, token) => { + tokenMap[token.address] = token + return tokenMap + }, + // must make a copy because reduce modifies the map, and we do not + // want to make a copy in every iteration + { ...tokensFromMap } + ) + ) + }, [tokensFromMap, userAddedTokens]) } type BridgeInfo = Record< @@ -73,7 +61,7 @@ export function useUnsupportedTokens(): { [address: string]: Token } { const { chainId } = useWeb3React() const listsByUrl = useAllLists() const unsupportedTokensMap = useUnsupportedTokenList() - const unsupportedTokens = useTokensFromMap(unsupportedTokensMap, false) + const unsupportedTokens = useTokensFromMap(unsupportedTokensMap) // checks the default L2 lists to see if `bridgeInfo` has an L1 address value that is unsupported const l2InferredBlockedTokens: typeof unsupportedTokens = useMemo(() => { diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index b679950d3a..21a16abbaa 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -8,6 +8,7 @@ import JSBI from 'jsbi' import { useCallback, useMemo } from 'react' import { shallowEqual } from 'react-redux' import { useAppDispatch, useAppSelector } from 'state/hooks' +import { UserAddedToken } from 'types/tokens' import { V2_FACTORY_ADDRESSES } from '../../constants/addresses' import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants/routing' @@ -38,8 +39,8 @@ function serializeToken(token: Token): SerializedToken { } } -function deserializeToken(serializedToken: SerializedToken): Token { - return new Token( +function deserializeToken(serializedToken: SerializedToken, Class: typeof Token = Token): Token { + return new Class( serializedToken.chainId, serializedToken.address, serializedToken.decimals, @@ -238,7 +239,7 @@ export function useUserAddedTokensOnChain(chainId: number | undefined | null): T return useMemo(() => { if (!chainId) return [] const tokenMap: Token[] = serializedTokensMap?.[chainId] - ? Object.values(serializedTokensMap[chainId]).map(deserializeToken) + ? Object.values(serializedTokensMap[chainId]).map((value) => deserializeToken(value, UserAddedToken)) : [] return tokenMap }, [serializedTokensMap, chainId]) diff --git a/src/types/tokens.ts b/src/types/tokens.ts new file mode 100644 index 0000000000..1e2f57c34c --- /dev/null +++ b/src/types/tokens.ts @@ -0,0 +1,3 @@ +import { Token } from '@uniswap/sdk-core' + +export class UserAddedToken extends Token {}