Ok. After a 20 minute rant about âobservabilityâ, in the control systems engineering sense of the word, I have this question: âIs there a way to generate debug output from a hook without triggering a re-render?â
Anyway after some fiddling around and cleanup my hook code looks like this:
import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import coinGeckoList from "../data/coinGeckoTokenList.json";
const emptyList = [];
const geckoHead =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";
export const usePositions = () => {
const { isAuthenticated, Moralis } = useMoralis();
const [positions, setPositions] = useState(emptyList);
const [totalValue, setTotalValue] = useState(0);
const [isLoading, setIsLoading] = useState(true);
// const [allPositions, setAllPositions] = useState(emptyList);
useEffect(() => {
if (isAuthenticated) {
// Bring back a list of all tokens the user has
console.log("Calling getAllERC20()...");
Moralis.Web3.getAllERC20().then((allPositions) => {
console.log("All position data:", allPositions);
const ids = allPositions
.map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
.filter((id) => Boolean(id))
.join(",");
const url = `${geckoHead}?vs_currency=usd&ids=${ids}` + geckoTail;
console.log(url);
// Call CoinGecko API:
fetch(url)
.then((response) => response.json())
.then((data) => {
// Convert to a 'dictionary' array of objects.
const marketData = {};
data.forEach((d) => (marketData[d.symbol.toUpperCase()] = d));
return marketData;
})
.then((data) => {
let runningTotal = 0;
const newList = allPositions.map((token) => {
// Merge position data with market data and augment.
const output = { ...token };
const tokenData = data[token.symbol.toUpperCase()];
output.image = tokenData?.image;
output.price = tokenData?.current_price;
output.tokens = token.balance
? token.balance / 10 ** token.decimals
: 0;
output.value = output.price ? output.tokens * output.price : 0;
runningTotal += output.value;
output.valueString = [
parseFloat(output?.tokens).toPrecision(3) +
" @ $" +
parseFloat(tokenData?.current_price).toFixed(2) +
"/" +
output?.symbol.toUpperCase() +
" = $" +
parseFloat(output?.value).toFixed(2),
];
return output;
});
// Done. Report back to states.
setPositions(newList);
setTotalValue(runningTotal);
setIsLoading(false);
});
});
} else {
// No authentication. Report blanks.
setPositions(emptyList);
setIsLoading(true);
}
}, [Moralis.Web3, isAuthenticated]);
return { positions, isLoading, totalValue };
};
And my component code looks like this:
import { Avatar, Flex, Text, VStack } from "@chakra-ui/react";
import {
Accordion,
AccordionItem,
AccordionButton,
AccordionPanel,
AccordionIcon,
} from "@chakra-ui/react";
import { usePositions } from "../../hooks/usePositions";
export const TokenTable = () => {
const { positions, isLoading, totalValue } = usePositions();
console.groupCollapsed("TokenTable");
console.log(!isLoading && positions);
return (
<VStack borderWidth={2} borderRadius={10} width="100%" padding={5}>
{!isLoading && <Text>Total Value: ${totalValue}</Text>}
<Accordion allowToggle width="100%">
{!isLoading &&
positions.map((position) => (
<AccordionItem key={position.name} width="100%">
<AccordionButton>
<Flex
width="100%"
alignItems="left"
justifyContent="space-between"
>
<Avatar
name={position.symbol}
src={position.image}
size="sm"
/>
<Text ml={2}>{position.name}</Text>
<Text ml={2}>{position.valueString}</Text>
</Flex>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<Text>Transaction list should go here.</Text>
</AccordionPanel>
</AccordionItem>
))}
</Accordion>
</VStack>
);
};
And together they run like this:
And this:

So in the hierarchy of programming needs:
- Does it compile? Yes.
- Does it run? Yes.
- Does it retrieve the data it is supposed to retrieve? Yes.
- Does it process the data as we expect? Yes.
- Does it return the right data? Yes.
- Does it display the right data? Yes.
- Is the data numerically correct? No.
Almost there. As you can see itâs returning some bogus token for Uniswap. I think itâs a problem with coinGeckoTokenList.json, but I havenât tracked it down yet. There are multiple tokens with the same symbol in the table. How do we sort out which the user really has? Can I get contract addresses returned from getAllERC20()? Keying off addresses would solve the problem, but then weâd need a table with addresses to key on.
The other way to âfixâ it would be to combine token.name and token.symbol and key off THAT. But thatâs not robust against scam copy tokens.

Thoughts?