From b926b5853a34621fe542bc30b6440c19d4ed7a8d Mon Sep 17 00:00:00 2001 From: David Uhlmann Date: Fri, 9 Feb 2024 02:36:55 +1000 Subject: [PATCH] feat: reduce unneccessary map creation before reference comparison --- src/lib/web3/hooks/useDenomClients.ts | 62 +++++++++--------------- src/lib/web3/hooks/useDenomsFromChain.ts | 44 ++++++----------- src/lib/web3/hooks/useSWR.ts | 52 +++++++++++++++++++- 3 files changed, 90 insertions(+), 68 deletions(-) diff --git a/src/lib/web3/hooks/useDenomClients.ts b/src/lib/web3/hooks/useDenomClients.ts index a4311e5e7..3c41c21c3 100644 --- a/src/lib/web3/hooks/useDenomClients.ts +++ b/src/lib/web3/hooks/useDenomClients.ts @@ -1,6 +1,6 @@ import useSWRImmutable from 'swr/immutable'; import { useQueries } from '@tanstack/react-query'; -import { useMemo, useRef } from 'react'; +import { useMemo } from 'react'; import { useDeepCompareMemoize } from 'use-deep-compare-effect'; import { ChainRegistryClient, @@ -13,7 +13,7 @@ import { AdditionalMintageTrace, Asset, Chain } from '@chain-registry/types'; import { useDenomTrace, useDenomTraceByDenom } from './useDenomsFromChain'; import { Token } from '../utils/tokens'; import { getAssetClient } from './useDenomsFromRegistry'; -import { SWRCommon, isEqualMap, useSwrResponse } from './useSWR'; +import { SWRCommon, useCombineResults, useSwrResponse } from './useSWR'; const { REACT_APP__CHAIN_NAME = '' } = import.meta.env; @@ -47,8 +47,7 @@ function useAssetClientByDenom( const swr1 = useDenomTraceByDenom(uniqueDenoms); const { data: denomTraceByDenom } = swr1; - const memoizedData = useRef(); - const { data: clientByDenom, ...swr2 } = useQueries({ + const { data: results, ...swr2 } = useQueries({ queries: uniqueDenoms.flatMap((denom) => { const trace = denomTraceByDenom?.get(denom); return { @@ -64,42 +63,29 @@ function useAssetClientByDenom( refetchOnWindowFocus: false, }; }), - combine: (results) => { - // compute data - const data = results.reduce( - (map, { isPending, data: [denom, client] = [] }) => { - // if resolved then add data - if (!isPending && denom) { - const chainUtil = client?.getChainUtil(REACT_APP__CHAIN_NAME); - const asset = chainUtil?.getAssetByDenom(denom); - // if the client if found, return that - if (client && asset) { - return map.set(denom, client); - } - // if the client is undefined (pending) or null (not found/correct) - else { - return map.set(denom, client ? null : client); - } - } - return map; - }, - new Map() - ); + // use generic simple as possible combination + combine: useCombineResults(), + }); - // update the memoized reference if the new data is different - if (!isEqualMap(data, memoizedData.current)) { - memoizedData.current = data; + const clientByDenom = useMemo(() => { + // compute map + return results.reduce((map, [denom, client]) => { + // if resolved then add data + if (denom) { + const chainUtil = client?.getChainUtil(REACT_APP__CHAIN_NAME); + const asset = chainUtil?.getAssetByDenom(denom); + // if the client if found, return that + if (client && asset) { + return map.set(denom, client); + } + // if the client is undefined (pending) or null (not found/correct) + else { + return map.set(denom, client ? null : client); + } } - - // return memoized data and combined result state - return { - data: memoizedData.current, - isLoading: results.every((result) => result.isPending), - isValidating: results.some((result) => result.isFetching), - error: results.find((result) => result.error)?.error, - }; - }, - }); + return map; + }, new Map()); + }, [results]); return useSwrResponse(clientByDenom, swr1, swr2); } diff --git a/src/lib/web3/hooks/useDenomsFromChain.ts b/src/lib/web3/hooks/useDenomsFromChain.ts index 9a46f68dc..be7acb167 100644 --- a/src/lib/web3/hooks/useDenomsFromChain.ts +++ b/src/lib/web3/hooks/useDenomsFromChain.ts @@ -1,11 +1,11 @@ import { useQueries } from '@tanstack/react-query'; import { useDeepCompareMemoize } from 'use-deep-compare-effect'; -import { useRef } from 'react'; +import { useMemo } from 'react'; import { DenomTrace } from '@duality-labs/neutronjs/types/codegen/ibc/applications/transfer/v1/transfer'; import { useIbcRestClient } from '../clients/restClients'; import { useDefaultDenomTraceByDenom } from './useDenomsFromRegistry'; -import { SWRCommon, isEqualMap, useSwrResponse } from './useSWR'; +import { SWRCommon, useCombineResults, useSwrResponse } from './useSWR'; type DenomTraceByDenom = Map; @@ -29,8 +29,7 @@ export function useDenomTraceByDenom( const restClient = useIbcRestClient(); - const memoizedData = useRef(); - const { data: denomTraceByDenom, ...swr } = useQueries({ + const { data: results, ...swr } = useQueries({ queries: ibcDenoms.flatMap((denom) => { const hash = denom.split('ibc/').at(1); if (restClient && hash) { @@ -63,33 +62,20 @@ export function useDenomTraceByDenom( } return []; }), - combine: (results) => { - // compute data - const data = results.reduce( - (map, { data: [denom, trace] = [] }) => { - // if resolved then add data - if (denom && trace) { - return map.set(denom, trace); - } - return map; - }, - new Map() - ); + // use generic simple as possible combination + combine: useCombineResults(), + }); - // update the memoized reference if the new data is different - if (!isEqualMap(data, memoizedData.current)) { - memoizedData.current = data; + const denomTraceByDenom = useMemo(() => { + // compute map + return results.reduce((map, [denom, trace]) => { + // if resolved then add data + if (denom && trace) { + return map.set(denom, trace); } - - // return memoized data and combined result state - return { - data: memoizedData.current, - isLoading: results.every((result) => result.isPending), - isValidating: results.some((result) => result.isFetching), - error: results.find((result) => result.error)?.error, - }; - }, - }); + return map; + }, new Map()); + }, [results]); return useSwrResponse(denomTraceByDenom, swr); } diff --git a/src/lib/web3/hooks/useSWR.ts b/src/lib/web3/hooks/useSWR.ts index 7ded93ce7..61c592270 100644 --- a/src/lib/web3/hooks/useSWR.ts +++ b/src/lib/web3/hooks/useSWR.ts @@ -1,11 +1,15 @@ import { UseQueryResult } from '@tanstack/react-query'; -import { useMemo } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import { SWRResponse } from 'swr'; export type SWRCommon = Omit< SWRResponse, 'mutate' >; +interface SWRCommonWithRequiredData + extends SWRCommon { + data: Data; +} export function useSwrResponse( data: T | undefined, @@ -54,6 +58,52 @@ export function useSwrResponseFromReactQuery( return useSwrResponse(data, swr1, swr2); } +export function useCombineResults(): ( + results: UseQueryResult[] +) => SWRCommonWithRequiredData { + const memoizedData = useRef([]); + return useCallback((results: UseQueryResult[]) => { + const data = results.flatMap((result) => + result.data ? [result.data] : [] + ); + // update the memoized reference if the new data is different + if (!isEqualArray(data, memoizedData.current)) { + memoizedData.current = data; + } + // return memoized data and combined result state + return { + data: memoizedData.current, + isLoading: results.every((result) => result.isPending), + isValidating: results.some((result) => result.isFetching), + error: results.find((result) => result.error)?.error ?? undefined, + }; + }, []); +} + +export function isEqualArray( + array1: Array, + array2: Array = new Array() +): boolean { + // compare array values if they are the same length + if (array1.length === array2.length) { + const length = array1.length; + for (let i = 0; i < length; i++) { + const value1 = array1[i]; + const value2 = array2[i]; + if (value1 !== value2) { + // an item is different + return false; + } + } + // no changes found + return true; + } + // the array length is different + else { + return false; + } +} + export function isEqualMap( map1: Map, map2: Map = new Map()