diff --git a/src/components/BalancesList.js b/src/components/BalancesList.js
index 0af0ae8..3a5cec6 100644
--- a/src/components/BalancesList.js
+++ b/src/components/BalancesList.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useMemo, useCallback, useEffect } from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
@@ -61,6 +61,24 @@ const balanceFormat = new Intl.NumberFormat(undefined, {
useGrouping: true,
});
+// Aggregated $USD values of all child BalanceListItems child components.
+//
+// Values:
+// * undefined => loading.
+// * null => no market exists.
+// * float => done.
+//
+// For a given set of publicKeys, we know all the USD values have been loaded when
+// all of their values in this object are not `undefined`.
+const usdValues = {};
+
+function fairsIsLoaded(publicKeys) {
+ return (
+ publicKeys.filter((pk) => usdValues[pk.toString()] !== undefined).length ===
+ publicKeys.length
+ );
+}
+
export default function BalancesList() {
const wallet = useWallet();
const [publicKeys, loaded] = useWalletPublicKeys();
@@ -69,22 +87,64 @@ export default function BalancesList() {
false,
);
const [showMergeAccounts, setShowMergeAccounts] = useState(false);
- const [usdValues, setUsdValues] = useState({});
const { accounts, setAccountName } = useWalletSelector();
+ // Dummy var to force rerenders on demand.
+ const [, setForceUpdate] = useState(false);
const selectedAccount = accounts.find((a) => a.isSelected);
- let totalUsdValue = publicKeys
- .filter((pk) => usdValues[pk.toString()] !== undefined)
+ const totalUsdValue = publicKeys
+ .filter((pk) => usdValues[pk.toString()])
.map((pk) => usdValues[pk.toString()])
.reduce((a, b) => a + b, 0.0);
+ // Memoized callback and component for the `BalanceListItems`.
+ //
+ // The `BalancesList` fetches data, e.g., fairs for tokens using React hooks
+ // in each of the child `BalanceListItem` components. However, we want the
+ // parent component, to aggregate all of this data together, for example,
+ // to show the cumulative USD amount in the wallet.
+ //
+ // To achieve this, we need to pass a callback from the parent to the chlid,
+ // so that the parent can collect the results of all the async network requests.
+ // However, this can cause a render loop, since invoking the callback can cause
+ // the parent to rerender, which causese the child to rerender, which causes
+ // the callback to be invoked.
+ //
+ // To solve this, we memoize all the `BalanceListItem` children components.
+ const setUsdValuesCallback = useCallback(
+ (publicKey, usdValue) => {
+ if (usdValues[publicKey.toString()] !== usdValue) {
+ usdValues[publicKey.toString()] = usdValue;
+ if (fairsIsLoaded(publicKeys)) {
+ setForceUpdate((forceUpdate) => !forceUpdate);
+ }
+ }
+ },
+ [publicKeys],
+ );
+ const balanceListItemsMemo = useMemo(() => {
+ return publicKeys.map((pk) => {
+ return React.memo((props) => {
+ return (
+
+ );
+ });
+ });
+ }, [publicKeys, setUsdValuesCallback]);
+
return (
- {selectedAccount && selectedAccount.name} Balances ($
- {totalUsdValue.toFixed(2)})
+ {selectedAccount && selectedAccount.name} Balances{' '}
+ {loaded && fairsIsLoaded(publicKeys) && (
+ <>(${totalUsdValue.toFixed(2)})>
+ )}
{selectedAccount &&
selectedAccount.name !== 'Main account' &&
@@ -121,18 +181,8 @@ export default function BalancesList() {
- {publicKeys.map((publicKey) => (
- {
- if (usdValues[publicKey.toString()] !== usdValue) {
- const _usdValues = { ...usdValues };
- _usdValues[publicKey.toString()] = usdValue;
- setUsdValues(_usdValues);
- }
- }}
- />
+ {balanceListItemsMemo.map((Memoized) => (
+
))}
{loaded ? null : }
@@ -249,11 +299,18 @@ export function BalanceListItem({ publicKey, expandable, setUsdValue }) {
);
- const usdValue = price
- ? ((amount / Math.pow(10, decimals)) * price).toFixed(2)
- : undefined;
- if (usdValue && setUsdValue) {
- setUsdValue(parseFloat(usdValue));
+
+ console.log('balance', publicKey.toString(), amount, balanceInfo);
+ console.log('price', price);
+ const usdValue =
+ price === undefined // Not yet loaded.
+ ? undefined
+ : price === null // Loaded and empty.
+ ? null
+ : ((amount / Math.pow(10, decimals)) * price).toFixed(2); // Loaded.
+ if (setUsdValue && usdValue !== undefined) {
+ console.log('sd calc', usdValue, amount, price, decimals);
+ setUsdValue(publicKey, usdValue === null ? null : parseFloat(usdValue));
}
return (
diff --git a/src/utils/markets.ts b/src/utils/markets.ts
index 5d1e8af..8b65fae 100644
--- a/src/utils/markets.ts
+++ b/src/utils/markets.ts
@@ -53,9 +53,9 @@ class PriceStore {
if (resp.data.asks.length === 0 && resp.data.bids.length === 0) {
resolve(undefined);
} else if (resp.data.asks.length === 0) {
- resolve(resp.data.bids[0]);
+ resolve(resp.data.bids[0].price);
} else if (resp.data.bids.length === 0) {
- resolve(resp.data.asks[0]);
+ resolve(resp.data.asks[0].price);
} else {
const mid =
(resp.data.asks[0].price + resp.data.bids[0].price) / 2.0;