Skip to content

Commit

Permalink
feat: reduce unneccessary map creation before reference comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
dib542 committed Feb 8, 2024
1 parent a61c01e commit b926b58
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 68 deletions.
62 changes: 24 additions & 38 deletions src/lib/web3/hooks/useDenomClients.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -47,8 +47,7 @@ function useAssetClientByDenom(
const swr1 = useDenomTraceByDenom(uniqueDenoms);
const { data: denomTraceByDenom } = swr1;

const memoizedData = useRef<AssetClientByDenom>();
const { data: clientByDenom, ...swr2 } = useQueries({
const { data: results, ...swr2 } = useQueries({
queries: uniqueDenoms.flatMap((denom) => {
const trace = denomTraceByDenom?.get(denom);
return {
Expand All @@ -64,42 +63,29 @@ function useAssetClientByDenom(
refetchOnWindowFocus: false,
};
}),
combine: (results) => {
// compute data
const data = results.reduce<AssetClientByDenom>(
(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<AssetClientByDenom>((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);
}
Expand Down
44 changes: 15 additions & 29 deletions src/lib/web3/hooks/useDenomsFromChain.ts
Original file line number Diff line number Diff line change
@@ -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<string, DenomTrace>;

Expand All @@ -29,8 +29,7 @@ export function useDenomTraceByDenom(

const restClient = useIbcRestClient();

const memoizedData = useRef<DenomTraceByDenom>();
const { data: denomTraceByDenom, ...swr } = useQueries({
const { data: results, ...swr } = useQueries({
queries: ibcDenoms.flatMap((denom) => {
const hash = denom.split('ibc/').at(1);
if (restClient && hash) {
Expand Down Expand Up @@ -63,33 +62,20 @@ export function useDenomTraceByDenom(
}
return [];
}),
combine: (results) => {
// compute data
const data = results.reduce<DenomTraceByDenom>(
(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<DenomTraceByDenom>((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);
}
Expand Down
52 changes: 51 additions & 1 deletion src/lib/web3/hooks/useSWR.ts
Original file line number Diff line number Diff line change
@@ -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<Data = unknown, Error = unknown> = Omit<
SWRResponse<Data, Error>,
'mutate'
>;
interface SWRCommonWithRequiredData<Data = unknown, Error = unknown>
extends SWRCommon<Data, Error> {
data: Data;
}

export function useSwrResponse<T>(
data: T | undefined,
Expand Down Expand Up @@ -54,6 +58,52 @@ export function useSwrResponseFromReactQuery<T>(
return useSwrResponse(data, swr1, swr2);
}

export function useCombineResults<Data, Error>(): (
results: UseQueryResult<Data, Error>[]
) => SWRCommonWithRequiredData<Data[], Error> {
const memoizedData = useRef<Data[]>([]);
return useCallback((results: UseQueryResult<Data, Error>[]) => {
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<V>(
array1: Array<V>,
array2: Array<V> = new Array<V>()
): 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<K, V>(
map1: Map<K, V>,
map2: Map<K, V> = new Map<K, V>()
Expand Down

0 comments on commit b926b58

Please sign in to comment.