1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 18:41:38 +01:00
metamask-extension/ui/app/hooks/useTokenTracker.js

109 lines
3.6 KiB
JavaScript

import { useState, useEffect, useRef, useCallback } from 'react';
import TokenTracker from '@metamask/eth-token-tracker';
import { useSelector } from 'react-redux';
import { getCurrentChainId, getSelectedAddress } from '../selectors';
import { useEqualityCheck } from './useEqualityCheck';
export function useTokenTracker(
tokens,
includeFailedTokens = false,
hideZeroBalanceTokens = false,
) {
const chainId = useSelector(getCurrentChainId);
const userAddress = useSelector(getSelectedAddress);
const [loading, setLoading] = useState(() => tokens?.length >= 0);
const [tokensWithBalances, setTokensWithBalances] = useState([]);
const [error, setError] = useState(null);
const tokenTracker = useRef(null);
const memoizedTokens = useEqualityCheck(tokens);
const updateBalances = useCallback(
(tokenWithBalances) => {
const matchingTokens = hideZeroBalanceTokens
? tokenWithBalances.filter((token) => Number(token.balance) > 0)
: tokenWithBalances;
setTokensWithBalances(matchingTokens);
setLoading(false);
setError(null);
},
[hideZeroBalanceTokens],
);
const showError = useCallback((err) => {
setError(err);
setLoading(false);
}, []);
const teardownTracker = useCallback(() => {
if (tokenTracker.current) {
tokenTracker.current.stop();
tokenTracker.current.removeAllListeners('update');
tokenTracker.current.removeAllListeners('error');
tokenTracker.current = null;
}
}, []);
const buildTracker = useCallback(
(address, tokenList) => {
// clear out previous tracker, if it exists.
teardownTracker();
tokenTracker.current = new TokenTracker({
userAddress: address,
provider: global.ethereumProvider,
tokens: tokenList,
includeFailedTokens,
pollingInterval: 8000,
});
tokenTracker.current.on('update', updateBalances);
tokenTracker.current.on('error', showError);
tokenTracker.current.updateBalances();
},
[updateBalances, includeFailedTokens, showError, teardownTracker],
);
// Effect to remove the tracker when the component is removed from DOM
// Do not overload this effect with additional dependencies. teardownTracker
// is the only dependency here, which itself has no dependencies and will
// never update. The lack of dependencies that change is what confirms
// that this effect only runs on mount/unmount
useEffect(() => {
return teardownTracker;
}, [teardownTracker]);
// Effect to set loading state and initialize tracker when values change
useEffect(() => {
// This effect will only run initially and when:
// 1. chainId is updated,
// 2. userAddress is changed,
// 3. token list is updated and not equal to previous list
// in any of these scenarios, we should indicate to the user that their token
// values are in the process of updating by setting loading state.
setLoading(true);
if (!userAddress || chainId === undefined || !global.ethereumProvider) {
// If we do not have enough information to build a TokenTracker, we exit early
// When the values above change, the effect will be restarted. We also teardown
// tracker because inevitably this effect will run again momentarily.
teardownTracker();
return;
}
if (memoizedTokens.length === 0) {
// sets loading state to false and token list to empty
updateBalances([]);
}
buildTracker(userAddress, memoizedTokens);
}, [
userAddress,
teardownTracker,
chainId,
memoizedTokens,
updateBalances,
buildTracker,
]);
return { loading, tokensWithBalances, error };
}