Skip to content

Commit

Permalink
perf: Use query instead of queries to reduce rerenders initially (#10899
Browse files Browse the repository at this point in the history
)

<!--
Before opening a pull request, please read the [contributing
guidelines](https://github.com/pancakeswap/pancake-frontend/blob/develop/CONTRIBUTING.md)
first
-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring multiple hooks in the codebase to replace
`useQueries` with `useQuery`, simplifying the data fetching logic and
improving error handling across various components.

### Detailed summary
- Replaced `useQueries` with `useQuery` in several hooks.
- Streamlined query functions to use `Promise.all` for concurrent
fetching.
- Enhanced error handling with `console.error` in query functions.
- Updated query keys to utilize `join` for better string formatting.
- Simplified memoization of results using `useMemo`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your
question}`

<!-- end pr-codex -->
  • Loading branch information
memoyil authored Nov 8, 2024
1 parent ad8519f commit 510522b
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 266 deletions.
107 changes: 56 additions & 51 deletions apps/web/src/hooks/v3/usePools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useActiveChainId } from 'hooks/useActiveChainId'
import { useMemo } from 'react'
import { Address } from 'viem'
import { publicClient } from 'utils/viem'
import { keepPreviousData, useQueries } from '@tanstack/react-query'
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { QUERY_SETTINGS_WITHOUT_INTERVAL_REFETCH, QUERY_SETTINGS_IMMUTABLE } from 'config/constants'
import { useMultipleContractSingleData } from 'state/multicall/hooks'
import { PoolState } from './types'
Expand Down Expand Up @@ -190,58 +190,63 @@ export function usePoolsWithMultiChains(
}, [poolTokens])

const chainIds = useMemo(() => Object.keys(poolAddressMap), [poolAddressMap])
const poolAddressesString = useMemo(
() => chainIds.flatMap((chainId) => poolAddressMap[chainId]).join(','),
[chainIds, poolAddressMap],
)

const queries = chainIds.map((chainId) => {
const poolAddresses: Address[] = poolAddressMap[chainId]
return {
queryKey: ['slot0', 'liquidity', chainId, ...poolAddresses],
placeholderData: keepPreviousData,
queryFn: async () => {
const client = publicClient({ chainId: Number(chainId) })

const slot0Calls = poolAddresses.map((addr) => ({
address: addr,
abi: v3PoolStateABI,
functionName: 'slot0',
}))

const liquidityCalls = poolAddresses.map((addr) => ({
address: addr,
abi: v3PoolStateABI,
functionName: 'liquidity',
}))

return Promise.all([
client.multicall({
contracts: slot0Calls,
allowFailure: false,
}),
client.multicall({
contracts: liquidityCalls,
allowFailure: false,
}),
])
},
enabled: !!chainId && !!poolAddresses.length,
...QUERY_SETTINGS_IMMUTABLE,
...QUERY_SETTINGS_WITHOUT_INTERVAL_REFETCH,
}
})
const poolInfoQueries = useQueries({
queries,
const { data: poolInfoByChainId } = useQuery({
queryKey: ['v3PoolInfo', chainIds.join(','), poolAddressesString],
queryFn: async () => {
const results = await Promise.all(
chainIds.map(async (chainId) => {
const poolAddresses: Address[] = poolAddressMap[chainId]
const client = publicClient({ chainId: Number(chainId) })

const slot0Calls = poolAddresses.map((addr) => ({
address: addr,
abi: v3PoolStateABI,
functionName: 'slot0',
}))

const liquidityCalls = poolAddresses.map((addr) => ({
address: addr,
abi: v3PoolStateABI,
functionName: 'liquidity',
}))

try {
const [slot0Data, liquidityData] = await Promise.all([
client.multicall({
contracts: slot0Calls,
allowFailure: false,
}),
client.multicall({
contracts: liquidityCalls,
allowFailure: false,
}),
])

return { [chainId]: { slot0: slot0Data, liquidity: liquidityData } }
} catch (error) {
console.error(`Error fetching data for chainId ${chainId}:`, error)
return { [chainId]: { slot0: undefined, liquidity: undefined } }
}
}),
)
return results.reduce((acc, result) => {
const chainId = Object.keys(result)[0]
// eslint-disable-next-line no-param-reassign
acc[chainId] = result[chainId]
return acc
}, {} as { slot0: [bigint, number, number, number, number, number, boolean]; liquidity: [bigint, number, number, number, number, number, boolean] })
},
enabled: chainIds?.length > 0,
placeholderData: keepPreviousData,
...QUERY_SETTINGS_IMMUTABLE,
...QUERY_SETTINGS_WITHOUT_INTERVAL_REFETCH,
})

const poolInfoByChainId = useMemo(() => {
return poolInfoQueries.reduce((acc, query, idx) => {
const chainId = chainIds[idx]
// eslint-disable-next-line no-param-reassign
acc[chainId] = acc[chainId] ?? []
// eslint-disable-next-line no-param-reassign
acc[chainId] = query.data
return acc
}, {})
}, [chainIds, poolInfoQueries])

return useMemo(() => {
const indexMap = {}
return poolKeys.map((_key, index) => {
Expand All @@ -250,7 +255,7 @@ export function usePoolsWithMultiChains(
const [token0, token1, fee] = tokens

indexMap[token0.chainId] = (indexMap[token0.chainId] ?? -1) + 1
const [slot0s = [], liquidities = []] = poolInfoByChainId[token0.chainId] || []
const { slot0: slot0s = [], liquidity: liquidities = [] } = poolInfoByChainId?.[token0.chainId] || {}
const slot0 = slot0s[indexMap[token0.chainId]]
const liquidity = liquidities[indexMap[token0.chainId]]
if (typeof slot0 === 'undefined' || typeof liquidity === 'undefined') return [PoolState.INVALID, null]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
import { LegacyRouter } from '@pancakeswap/smart-router/legacy-router'
import { useQueries, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { SLOW_INTERVAL } from 'config/constants'
import { useCallback, useMemo } from 'react'
import { Address } from 'viem'
import { useMemo } from 'react'
import { getStablePairDetails } from '../fetcher'
import { StableLPDetail } from '../type'
import { useLatestTxReceipt } from './useLatestTxReceipt'

export const useAccountStableLpDetails = (chainIds: number[], account?: Address | null) => {
const [latestTxReceipt] = useLatestTxReceipt()
const queries = useMemo(() => {
return chainIds.map((chainId) => {
const stablePairs = LegacyRouter.stableSwapPairsByChainId[chainId]
return {
queryKey: ['accountStableLpBalance', account, chainId, latestTxReceipt?.blockHash],
// @todo @ChefJerry add signal
queryFn: () => getStablePairDetails(chainId, account!, stablePairs),
enabled: !!account && stablePairs && stablePairs.length > 0,
select(data) {
return data.filter((d) => d.nativeBalance.greaterThan('0') || d.farmingBalance.greaterThan('0'))
},
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: SLOW_INTERVAL,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
} satisfies UseQueryOptions<StableLPDetail[]>
})
}, [account, chainIds, latestTxReceipt?.blockHash])

const combine = useCallback((results: UseQueryResult<StableLPDetail[], Error>[]) => {
return {
data: results.reduce((acc, result) => acc.concat(result.data ?? []), [] as StableLPDetail[]),
pending: results.some((result) => result.isPending),
}
}, [])
return useQueries({
queries,
combine,
const { data, isPending } = useQuery<StableLPDetail[], Error>({
queryKey: ['accountStableLpBalance', account, chainIds.join(','), latestTxReceipt?.blockHash],
// @todo @ChefJerry add signal
queryFn: async () => {
if (!account) return []
const results = await Promise.all(
chainIds.map(async (chainId) => {
const stablePairs = LegacyRouter.stableSwapPairsByChainId[chainId]
if (!stablePairs || stablePairs.length === 0) return []
try {
const details = await getStablePairDetails(chainId, account, stablePairs)
return details.filter((d) => d.nativeBalance.greaterThan('0') || d.farmingBalance.greaterThan('0'))
} catch (error) {
console.error(`Error fetching details for chainId ${chainId}:`, error)
return []
}
}),
)
return results.flat()
},
enabled: !!account,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: SLOW_INTERVAL,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
})

return useMemo(
() => ({
data: data ?? [],
pending: isPending,
}),
[data, isPending],
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ERC20Token } from '@pancakeswap/sdk'
import { useQueries, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { SLOW_INTERVAL } from 'config/constants'
import { useOfficialsAndUserAddedTokensByChainIds } from 'hooks/Tokens'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useSelector } from 'react-redux'
import { AppState } from 'state'
import { Address } from 'viem'
Expand Down Expand Up @@ -34,39 +34,47 @@ export const useAccountV2LpDetails = (chainIds: number[], account?: Address | nu
fetchLpTokens()
}, [chainIds, tokens, userSavedPairs])

const [latestTxReceipt] = useLatestTxReceipt()
const queries = useMemo(() => {
if (!lpTokensByChain) {
return []
}
const totalTokenPairCount = useMemo(() => {
if (!lpTokensByChain) return 0

return Object.entries(lpTokensByChain).map(([chainId, lpTokens]) => {
return {
queryKey: ['accountV2LpDetails', account, chainId, lpTokens.length, latestTxReceipt?.blockHash],
// @todo @ChefJerry add signal
queryFn: () => getAccountV2LpDetails(Number(chainId), account!, lpTokens),
enabled: !!account && lpTokens && lpTokens.length > 0,
select(data) {
return data.filter((d) => d.nativeBalance.greaterThan('0') || d.farmingBalance.greaterThan('0'))
},
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
} satisfies UseQueryOptions<V2LPDetail[]>
})
}, [account, lpTokensByChain, latestTxReceipt?.blockHash])
return Object.values(lpTokensByChain).reduce((total, tokenPairs) => {
return total + tokenPairs.length
}, 0)
}, [lpTokensByChain])

const combine = useCallback((results: UseQueryResult<V2LPDetail[], Error>[]) => {
return {
data: results.reduce((acc, result) => acc.concat(result.data ?? []), [] as V2LPDetail[]),
pending: results.some((result) => result.isPending),
}
}, [])
const [latestTxReceipt] = useLatestTxReceipt()

return useQueries({
queries,
combine,
const query = useQuery<V2LPDetail[], Error>({
queryKey: ['accountV2LpDetails', account, chainIds.join('-'), totalTokenPairCount, latestTxReceipt?.blockHash],
queryFn: async () => {
if (!account || !lpTokensByChain) return []
const results = await Promise.all(
Object.entries(lpTokensByChain).map(async ([chainId, lpTokens]) => {
if (lpTokens.length === 0) return []
try {
const details = await getAccountV2LpDetails(Number(chainId), account, lpTokens)
return details.filter((d) => d.nativeBalance.greaterThan('0') || d.farmingBalance.greaterThan('0'))
} catch (error) {
console.error(`Error fetching LP details for chainId ${chainId}:`, error)
return []
}
}),
)
return results.flat()
},
enabled: !!account && !!lpTokensByChain,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
})

return useMemo(
() => ({
data: query.data ?? [],
pending: query.isLoading || query.isFetching,
}),
[query.data, query.isLoading, query.isFetching],
)
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
import { useQueries, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { SLOW_INTERVAL } from 'config/constants'
import { useCallback, useMemo } from 'react'
import { useMemo } from 'react'
import { Address } from 'viem'
import { getAccountV3Positions } from '../fetcher/v3'
import { PositionDetail } from '../type'
import { useLatestTxReceipt } from './useLatestTxReceipt'

export const useAccountV3Positions = (chainIds: number[], account?: Address | null) => {
const [latestTxReceipt] = useLatestTxReceipt()
const queries = useMemo(() => {
return chainIds.map((chainId) => {
return {
queryKey: ['accountV3Positions', account, chainId, latestTxReceipt?.blockHash],
// @todo @ChefJerry add signal
queryFn: () => getAccountV3Positions(chainId, account!),
enabled: !!account && !!chainId,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: SLOW_INTERVAL,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
} satisfies UseQueryOptions<PositionDetail[]>
})
}, [account, chainIds, latestTxReceipt?.blockHash])

const combine = useCallback((results: UseQueryResult<PositionDetail[], Error>[]) => {
return {
data: results.reduce((acc, result) => acc.concat(result.data ?? []), [] as PositionDetail[]),
pending: results.some((result) => result.isPending),
}
}, [])
return useQueries({
queries,
combine,
const { data, isPending } = useQuery<PositionDetail[], Error>({
queryKey: ['accountV3Positions', account, chainIds.join('-'), latestTxReceipt?.blockHash],
// @todo @ChefJerry add signal
queryFn: async () => {
if (!account) return []
const results = await Promise.all(
chainIds.map(async (chainId) => {
try {
const positions = await getAccountV3Positions(chainId, account)
return positions ?? []
} catch (error) {
console.error(`Error fetching V3 positions for chainId ${chainId}:`, error)
return []
}
}),
)
return results.flat()
},
enabled: !!account,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: SLOW_INTERVAL,
// Prevents re-fetching while the data is still fresh
staleTime: SLOW_INTERVAL,
})

// Memoize the result object
return useMemo(
() => ({
data: data ?? [],
pending: isPending,
}),
[data, isPending],
)
}
Loading

0 comments on commit 510522b

Please sign in to comment.