Memoize BalanceListItem components

This commit is contained in:
Armani Ferrante 2021-03-02 16:13:46 +08:00
parent bda5c4c323
commit dee1252057
No known key found for this signature in database
GPG Key ID: D597A80BCF8E12B7
2 changed files with 82 additions and 25 deletions

View File

@ -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 (
<BalanceListItem
key={pk.toString()}
publicKey={pk}
setUsdValue={setUsdValuesCallback}
/>
);
});
});
}, [publicKeys, setUsdValuesCallback]);
return (
<Paper>
<AppBar position="static" color="default" elevation={1}>
<Toolbar>
<Typography variant="h6" style={{ flexGrow: 1 }} component="h2">
{selectedAccount && selectedAccount.name} Balances ($
{totalUsdValue.toFixed(2)})
{selectedAccount && selectedAccount.name} Balances{' '}
{loaded && fairsIsLoaded(publicKeys) && (
<>(${totalUsdValue.toFixed(2)})</>
)}
</Typography>
{selectedAccount &&
selectedAccount.name !== 'Main account' &&
@ -121,18 +181,8 @@ export default function BalancesList() {
</Toolbar>
</AppBar>
<List disablePadding>
{publicKeys.map((publicKey) => (
<BalanceListItem
key={publicKey.toBase58()}
publicKey={publicKey}
setUsdValue={(usdValue) => {
if (usdValues[publicKey.toString()] !== usdValue) {
const _usdValues = { ...usdValues };
_usdValues[publicKey.toString()] = usdValue;
setUsdValues(_usdValues);
}
}}
/>
{balanceListItemsMemo.map((Memoized) => (
<Memoized />
))}
{loaded ? null : <LoadingIndicator />}
</List>
@ -249,11 +299,18 @@ export function BalanceListItem({ publicKey, expandable, setUsdValue }) {
</div>
</div>
);
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 (

View File

@ -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;