Skip to content

refactor: simplify query combination typing #1155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ const FormWithdraw = ({
])

const tokenAddresses = useMemo(() => formValues.amounts.map((a) => a.tokenAddress), [formValues.amounts])
const { data: usdRatesRaw } = useTokenUsdRates({ chainId, tokenAddresses })
const usdRates = useMemo(() => usdRatesRaw ?? ({} as Record<string, number>), [usdRatesRaw])
const { data: usdRates } = useTokenUsdRates({ chainId, tokenAddresses })

// usd amount for slippage warning
const estUsdAmountTotalReceive = useMemo(() => {
Expand Down
3 changes: 1 addition & 2 deletions apps/main/src/loan/hooks/useLoanAppStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ function useTvl(chainId: ChainId | undefined) {
[collateralDatasMapper],
)

const { data: usdRatesRaw } = useTokenUsdRates({ chainId, tokenAddresses: collateralTokenAddresses })
const usdRates = useMemo(() => usdRatesRaw ?? ({} as Record<string, number>), [usdRatesRaw])
const { data: usdRates } = useTokenUsdRates({ chainId, tokenAddresses: collateralTokenAddresses })

return useMemo(() => {
if (!hasKeys(collateralDatasMapper) || !hasKeys(loansDetailsMapper) || Object.keys(usdRates).length === 0) {
Expand Down
12 changes: 7 additions & 5 deletions packages/curve-ui-kit/src/lib/model/entities/token-usd-rate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { useQueries } from '@tanstack/react-query'
import { getLib, requireLib } from '@ui-kit/features/connect-wallet'
import { useQueryMapping } from '@ui-kit/lib'
import { combineQueriesToObject } from '@ui-kit/lib'
import { queryClient } from '@ui-kit/lib/api'
import {
extractTokenAddress,
Expand Down Expand Up @@ -30,10 +31,11 @@ export const {

export const useTokenUsdRates = ({ chainId, tokenAddresses = [] }: ChainParams & { tokenAddresses?: string[] }) => {
const uniqueAddresses = Array.from(new Set(tokenAddresses))
return useQueryMapping(
uniqueAddresses.map((tokenAddress) => getTokenUsdRateQueryOptions({ chainId, tokenAddress })),
uniqueAddresses,
)

return useQueries({
queries: uniqueAddresses.map((tokenAddress) => getTokenUsdRateQueryOptions({ chainId, tokenAddress })),
combine: (results) => combineQueriesToObject(results, uniqueAddresses),
})
}

/** Check if it's a token price query by looking for QUERY_KEY_IDENTIFIER as the last element */
Expand Down
44 changes: 8 additions & 36 deletions packages/curve-ui-kit/src/lib/queries/combine.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,20 @@
import { useCallback } from 'react'
import { useQueries } from '@tanstack/react-query'
import { QueriesOptions, QueriesResults } from '@tanstack/react-query/build/legacy/useQueries'
import {
CombinedQueriesResult,
CombinedQueryMappingResult,
ExtractDataType,
QueryOptionsArray,
QueryResultsArray,
} from './types'
import type { UseQueryOptions } from '@tanstack/react-query'
import { QueryOptionsArray, QueryResultsArray } from './types'

/** Combines the metadata of multiple queries into a single object. */
export const combineQueriesMeta = <T extends QueryOptionsArray>(
results: QueryResultsArray<T>,
): Omit<CombinedQueriesResult<T>, 'data'> => ({
export const combineQueriesMeta = <T extends QueryOptionsArray>(results: QueryResultsArray<T>) => ({
isLoading: results.some((result) => result.isLoading),
isPending: results.some((result) => result.isPending),
isError: results.some((result) => result.isError),
isFetching: results.some((result) => result.isFetching),
})

/** Combines the data and metadata of multiple queries into a single object. */
const combineQueriesToObject = <T extends QueryOptionsArray, K extends string[]>(
results: QueryResultsArray<T>,
export const combineQueriesToObject = <TData, K extends string[]>(
results: QueryResultsArray<UseQueryOptions<TData, any, any, any>[]>,
keys: K,
): CombinedQueryMappingResult<T, K> => ({
data: Object.fromEntries((results || []).map(({ data }, index) => [keys[index], data])) as Record<
K[number],
ExtractDataType<T[number]>
>,
) => ({
// Using flatMap instead of map + filter(Boolean), because it's not correctly erasing | undefined from the Record value type
data: Object.fromEntries(results.flatMap(({ data }, index) => (data !== undefined ? [[keys[index], data]] : []))),
...combineQueriesMeta(results),
})

/**
* Combines multiple queries into a single object with keys for each query
* @param queries The query options to combine
* @param keys The keys to use for each query
* @returns The combined queries in an object
*/
export const useQueryMapping = <TOptions extends Array<any>, TKey extends string[]>(
queries: readonly [...QueriesOptions<TOptions>],
keys: [...TKey],
) =>
useQueries({
// todo: figure out why the type has broken in react-query, related to https://github.com/TanStack/query/pull/8624
queries: queries as Parameters<typeof useQueries>[0]['queries'],
combine: useCallback((results: QueriesResults<TKey>) => combineQueriesToObject(results, keys), [keys]),
})
10 changes: 0 additions & 10 deletions packages/curve-ui-kit/src/lib/queries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'

export type QueryOptionsArray = readonly UseQueryOptions<any, any, any, any>[]

export type ExtractDataType<T> = T extends UseQueryOptions<infer TData, any, any, any> ? TData : unknown

export type CombinedDataType<T extends QueryOptionsArray> = ExtractDataType<T[number]>[]

export type QueryResultsArray<T extends QueryOptionsArray> = {
[K in keyof T]: T[K] extends UseQueryOptions<infer TData, any, any, any> ? UseQueryResult<TData> : never
}
Expand All @@ -14,9 +10,3 @@ export type PartialQueryResult<T> = Pick<
UseQueryResult<T>,
'data' | 'isLoading' | 'isPending' | 'isError' | 'isFetching'
>

export type CombinedQueriesResult<T extends QueryOptionsArray> = PartialQueryResult<CombinedDataType<T>>

export type CombinedQueryMappingResult<T extends QueryOptionsArray, K extends string[]> = PartialQueryResult<
Record<K[number], CombinedDataType<T>[number]>
>
Loading