Skip to content

Commit

Permalink
fix: useless multiple re-render
Browse files Browse the repository at this point in the history
  • Loading branch information
Majorfi committed Oct 16, 2023
1 parent 9ae42f8 commit 005a215
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 127 deletions.
127 changes: 125 additions & 2 deletions apps/vaults/hooks/useFilteredVaults.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,131 @@
import {useMemo} from 'react';
import {useCallback, useMemo} from 'react';
import {useDeepCompareMemo} from '@react-hookz/web';
import {OPT_VAULTS_WITH_REWARDS, STACKING_TO_VAULT} from '@vaults/constants/optRewards';
import {useAppSettings} from '@vaults/contexts/useAppSettings';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
import {useWallet} from '@common/contexts/useWallet';
import {useYearn} from '@common/contexts/useYearn';

import type {TDict} from '@yearn-finance/web-lib/types';
import type {TAddress, TDict} from '@yearn-finance/web-lib/types';
import type {TYDaemonVault} from '@common/schemas/yDaemonVaultsSchemas';

export function useFilteredVaults(vaultMap: TDict<TYDaemonVault>, condition: (v: TYDaemonVault) => boolean): TYDaemonVault[] {
return useMemo((): TYDaemonVault[] => Object.values(vaultMap).filter((vault): boolean => condition(vault)), [vaultMap, condition]);
}

export function useVaultFilter(): {activeVaults: TYDaemonVault[]; retiredVaults: TYDaemonVault[]; migratableVaults: TYDaemonVault[]} {
const {vaults, vaultsMigrations, vaultsRetired} = useYearn();
const {getToken} = useWallet();
const {shouldHideDust, category, selectedChains} = useAppSettings();
const chainsFromJSON = useMemo((): number[] => JSON.parse(selectedChains || '[]') as number[], [selectedChains]);
const categoriesFromJSON = useMemo((): string[] => JSON.parse(category || '[]') as string[], [category]);

const filterHoldingsCallback = useCallback(
(address: TAddress, chainID: number): boolean => {
const holding = getToken({address, chainID});

// [Optimism] Check if staked vaults have holdings
if (chainsFromJSON.includes(10)) {
const stakedVaultAddress = STACKING_TO_VAULT[toAddress(address)];
const stakedHolding = getToken({address: stakedVaultAddress, chainID});
const hasValidStakedBalance = stakedHolding.balance.raw > 0n;
const stakedBalanceValue = stakedHolding.value || 0;
if (hasValidStakedBalance && !(shouldHideDust && stakedBalanceValue < 0.01)) {
return true;
}
}

const hasValidBalance = holding.balance.raw > 0n;
const balanceValue = holding.value || 0;
if (shouldHideDust && balanceValue < 0.01) {
return false;
}
if (hasValidBalance) {
return true;
}
return false;
},
[getToken, chainsFromJSON, shouldHideDust]
);

const filterMigrationCallback = useCallback(
(address: TAddress, chainID: number): boolean => {
const holding = getToken({address, chainID});
const hasValidPrice = holding.price.raw > 0n;
const hasValidBalance = holding.balance.raw > 0n;
if (hasValidBalance && (hasValidPrice ? (holding?.value || 0) >= 0.01 : true)) {
return true;
}
return false;
},
[getToken]
);

const boostedVaults = useFilteredVaults(vaults, ({address}): boolean => {
if (chainsFromJSON.includes(10)) {
return false;
}
return OPT_VAULTS_WITH_REWARDS.some((token): boolean => token === address);
});
const curveVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Curve');
const velodromeVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Velodrome');
const aerodromeVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Aerodrome');
const stablesVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Stablecoin');
const balancerVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Balancer');
const cryptoVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Volatile');
const holdingsVaults = useFilteredVaults(vaults, ({address, chainID}): boolean => filterHoldingsCallback(address, chainID));
const migratableVaults = useFilteredVaults(vaultsMigrations, ({address, chainID}): boolean => filterMigrationCallback(address, chainID));
const retiredVaults = useFilteredVaults(vaultsRetired, ({address, chainID}): boolean => filterMigrationCallback(address, chainID));

/* 🔵 - Yearn Finance **************************************************************************
** First, we need to determine in which category we are. The activeVaults function will
** decide which vaults to display based on the category. No extra filters are applied.
** The possible lists are memoized to avoid unnecessary re-renders.
**********************************************************************************************/
const activeVaults = useDeepCompareMemo((): TYDaemonVault[] => {
let _vaultList: TYDaemonVault[] = [];

if (categoriesFromJSON.includes('Featured Vaults')) {
_vaultList.sort((a, b): number => (b.tvl.tvl || 0) * (b?.apr?.netAPR || 0) - (a.tvl.tvl || 0) * (a?.apr?.netAPR || 0));
_vaultList = _vaultList.slice(0, 10);
}
if (categoriesFromJSON.includes('Curve Vaults')) {
_vaultList = [..._vaultList, ...curveVaults];
}
if (categoriesFromJSON.includes('Balancer Vaults')) {
_vaultList = [..._vaultList, ...balancerVaults];
}
if (categoriesFromJSON.includes('Velodrome Vaults')) {
_vaultList = [..._vaultList, ...velodromeVaults];
}
if (categoriesFromJSON.includes('Aerodrome Vaults')) {
_vaultList = [..._vaultList, ...aerodromeVaults];
}
if (categoriesFromJSON.includes('Boosted Vaults')) {
_vaultList = [..._vaultList, ...boostedVaults];
}
if (categoriesFromJSON.includes('Stables Vaults')) {
_vaultList = [..._vaultList, ...stablesVaults];
}
if (categoriesFromJSON.includes('Crypto Vaults')) {
_vaultList = [..._vaultList, ...cryptoVaults];
}
if (categoriesFromJSON.includes('Holdings')) {
_vaultList = [..._vaultList, ...holdingsVaults];
}

//remove duplicates
_vaultList = _vaultList.filter((vault, index, self): boolean => index === self.findIndex((v): boolean => v.address === vault.address));

for (const vault of _vaultList) {
if (vault.apr.forwardAPR.netAPR === 0) {
// console.log(`DebtRatio for vault ${vault.address} - ${vault.name}: 0`);
}
}
// console.log('-------------------')

return _vaultList;
}, [categoriesFromJSON, curveVaults, balancerVaults, velodromeVaults, aerodromeVaults, boostedVaults, stablesVaults, cryptoVaults, holdingsVaults]);

return {activeVaults, migratableVaults, retiredVaults};
}
9 changes: 5 additions & 4 deletions apps/vaults/hooks/useSortVaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ export function useSortVaults(vaultList: TYDaemonVaults, sortBy: TPossibleSortBy
return sortedByName();
}
if (sortBy === 'forwardAPR') {
const sort = sortedByForwardAPR();
console.log(sort);
return sort;
return sortedByForwardAPR();
}
if (sortBy === 'apr') {
return sortedByAPR();
}
if (sortBy === 'tvl') {
return sortedByTVL();
Expand All @@ -111,7 +112,7 @@ export function useSortVaults(vaultList: TYDaemonVaults, sortBy: TPossibleSortBy
}

return sortResult;
}, [stringifiedVaultList, sortDirection, sortBy, sortedByName, sortedByForwardAPR, sortedByAPR, sortedByTVL, sortedByDeposited, sortedByAvailable, sortedByFeaturingScore]);
}, [stringifiedVaultList, sortDirection, sortBy, sortedByName, sortedByForwardAPR, sortedByTVL, sortedByAPR, sortedByDeposited, sortedByAvailable, sortedByFeaturingScore]);

return sortedVaults;
}
129 changes: 8 additions & 121 deletions pages/vaults/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import {VaultsListInternalMigrationRow} from '@vaults/components/list/VaultsList
import {VaultsListRetired} from '@vaults/components/list/VaultsListRetired';
import {VaultsListRow} from '@vaults/components/list/VaultsListRow';
import {ListHero} from '@vaults/components/ListHero';
import {OPT_VAULTS_WITH_REWARDS, STACKING_TO_VAULT} from '@vaults/constants/optRewards';
import {useAppSettings} from '@vaults/contexts/useAppSettings';
import {useFilteredVaults} from '@vaults/hooks/useFilteredVaults';
import {useVaultFilter} from '@vaults/hooks/useFilteredVaults';
import {useSortVaults} from '@vaults/hooks/useSortVaults';
import {Wrapper} from '@vaults/Wrapper';
import {Button} from '@yearn-finance/web-lib/components/Button';
import {Renderable} from '@yearn-finance/web-lib/components/Renderable';
import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
import {useSessionStorage} from '@yearn-finance/web-lib/hooks/useSessionStorage';
import {IconChain} from '@yearn-finance/web-lib/icons/IconChain';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
import {formatAmount} from '@yearn-finance/web-lib/utils/format.number';
import {isZero} from '@yearn-finance/web-lib/utils/isZero';
import {ListHead} from '@common/components/ListHead';
Expand All @@ -25,7 +23,6 @@ import {useYearn} from '@common/contexts/useYearn';

import type {NextRouter} from 'next/router';
import type {ReactElement, ReactNode} from 'react';
import type {TAddress} from '@yearn-finance/web-lib/types';
import type {TYDaemonVault} from '@common/schemas/yDaemonVaultsSchemas';
import type {TSortDirection} from '@common/types/types';
import type {TPossibleSortBy} from '@vaults/hooks/useSortVaults';
Expand Down Expand Up @@ -93,135 +90,25 @@ function HeaderUserPosition(): ReactElement {
}

function Index(): ReactElement {
const {getToken} = useWallet();
const {vaults, vaultsMigrations, vaultsRetired, isLoadingVaultList} = useYearn();
const {isLoadingVaultList} = useYearn();
const [sort, set_sort] = useSessionStorage<{
sortBy: TPossibleSortBy;
sortDirection: TSortDirection;
}>('yVaultsSorting', {sortBy: 'featuringScore', sortDirection: 'desc'});
const {shouldHideDust, category, searchValue, selectedChains, set_category, set_searchValue, set_selectedChains} = useAppSettings();

const {category, searchValue, selectedChains, set_category, set_searchValue, set_selectedChains} = useAppSettings();
const chainsFromJSON = useMemo((): number[] => JSON.parse(selectedChains || '[]') as number[], [selectedChains]);
const categoriesFromJSON = useMemo((): string[] => JSON.parse(category || '[]') as string[], [category]);
const filterHoldingsCallback = useCallback(
(address: TAddress, chainID: number): boolean => {
const holding = getToken({address, chainID});

// [Optimism] Check if staked vaults have holdings
if (chainsFromJSON.includes(10)) {
const stakedVaultAddress = STACKING_TO_VAULT[toAddress(address)];
const stakedHolding = getToken({address: stakedVaultAddress, chainID});
const hasValidStakedBalance = stakedHolding.balance.raw > 0n;
const stakedBalanceValue = stakedHolding.value || 0;
if (hasValidStakedBalance && !(shouldHideDust && stakedBalanceValue < 0.01)) {
return true;
}
}

const hasValidBalance = holding.balance.raw > 0n;
const balanceValue = holding.value || 0;
if (shouldHideDust && balanceValue < 0.01) {
return false;
}
if (hasValidBalance) {
return true;
}
return false;
},
[getToken, chainsFromJSON, shouldHideDust]
);

const filterMigrationCallback = useCallback(
(address: TAddress, chainID: number): boolean => {
const holding = getToken({address, chainID});
const hasValidPrice = holding.price.raw > 0n;
const hasValidBalance = holding.balance.raw > 0n;
if (hasValidBalance && (hasValidPrice ? (holding?.value || 0) >= 0.01 : true)) {
return true;
}
return false;
},
[getToken]
);

/* 🔵 - Yearn Finance **************************************************************************
** It's best to memorize the filtered vaults, which saves a lot of processing time by only
** performing the filtering once.
**********************************************************************************************/
const curveVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Curve');
const boostedVaults = useFilteredVaults(vaults, ({address}): boolean => {
if (chainsFromJSON.includes(10)) {
return false;
}
return OPT_VAULTS_WITH_REWARDS.some((token): boolean => token === address);
});
const velodromeVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Velodrome');
const aerodromeVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Aerodrome');
const stablesVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Stablecoin');
const balancerVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Balancer');
const cryptoVaults = useFilteredVaults(vaults, ({category}): boolean => category === 'Volatile');
const holdingsVaults = useFilteredVaults(vaults, ({address, chainID}): boolean => filterHoldingsCallback(address, chainID));
const migratableVaults = useFilteredVaults(vaultsMigrations, ({address, chainID}): boolean => filterMigrationCallback(address, chainID));
const retiredVaults = useFilteredVaults(vaultsRetired, ({address, chainID}): boolean => filterMigrationCallback(address, chainID));

/* 🔵 - Yearn Finance **************************************************************************
** First, we need to determine in which category we are. The vaultsToDisplay function will
** decide which vaults to display based on the category. No extra filters are applied.
** The possible lists are memoized to avoid unnecessary re-renders.
**********************************************************************************************/
const vaultsToDisplay = useMemo((): TYDaemonVault[] => {
let _vaultList: TYDaemonVault[] = [];

if (categoriesFromJSON.includes('Featured Vaults')) {
_vaultList.sort((a, b): number => (b.tvl.tvl || 0) * (b?.apr?.netAPR || 0) - (a.tvl.tvl || 0) * (a?.apr?.netAPR || 0));
_vaultList = _vaultList.slice(0, 10);
}
if (categoriesFromJSON.includes('Curve Vaults')) {
_vaultList = [..._vaultList, ...curveVaults];
}
if (categoriesFromJSON.includes('Balancer Vaults')) {
_vaultList = [..._vaultList, ...balancerVaults];
}
if (categoriesFromJSON.includes('Velodrome Vaults')) {
_vaultList = [..._vaultList, ...velodromeVaults];
}
if (categoriesFromJSON.includes('Aerodrome Vaults')) {
_vaultList = [..._vaultList, ...aerodromeVaults];
}
if (categoriesFromJSON.includes('Boosted Vaults')) {
_vaultList = [..._vaultList, ...boostedVaults];
}
if (categoriesFromJSON.includes('Stables Vaults')) {
_vaultList = [..._vaultList, ...stablesVaults];
}
if (categoriesFromJSON.includes('Crypto Vaults')) {
_vaultList = [..._vaultList, ...cryptoVaults];
}
if (categoriesFromJSON.includes('Holdings')) {
_vaultList = [..._vaultList, ...holdingsVaults];
}

//remove duplicates
_vaultList = _vaultList.filter((vault, index, self): boolean => index === self.findIndex((v): boolean => v.address === vault.address));

for (const vault of _vaultList) {
if (vault.apr.forwardAPR.type !== '' && vault.apr.forwardAPR.netAPR === 0) {
console.log(`DebtRatio for vault ${vault.address} - ${vault.name}: 0`);
}
}

return _vaultList;
}, [categoriesFromJSON, curveVaults, balancerVaults, velodromeVaults, aerodromeVaults, boostedVaults, stablesVaults, cryptoVaults, holdingsVaults]);
const {activeVaults, migratableVaults, retiredVaults} = useVaultFilter();

/* 🔵 - Yearn Finance **************************************************************************
** Then, on the vaultsToDisplay list, we apply the search filter. The search filter is
** Then, on the activeVaults list, we apply the search filter. The search filter is
** implemented as a simple string.includes() on the vault name.
**********************************************************************************************/
const searchedVaultsToDisplay = useMemo((): TYDaemonVault[] => {
if (searchValue === '') {
return vaultsToDisplay;
return activeVaults;
}
return vaultsToDisplay.filter((vault: TYDaemonVault): boolean => {
return activeVaults.filter((vault: TYDaemonVault): boolean => {
const lowercaseSearch = searchValue.toLowerCase();
return (
vault.name.toLowerCase().startsWith(lowercaseSearch) ||
Expand All @@ -232,7 +119,7 @@ function Index(): ReactElement {
vault.token.address.toLowerCase().startsWith(lowercaseSearch)
);
});
}, [vaultsToDisplay, searchValue]);
}, [activeVaults, searchValue]);

/* 🔵 - Yearn Finance **************************************************************************
** Then, once we have reduced the list of vaults to display, we can sort them. The sorting
Expand Down

0 comments on commit 005a215

Please sign in to comment.