Skip to content
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

fix(vue-query): useQueries type inference #7416

Merged
merged 1 commit into from
Jun 26, 2024
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
77 changes: 77 additions & 0 deletions packages/vue-query/src/__tests__/useQueries.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,81 @@ describe('UseQueries config object overload', () => {
expectTypeOf(queriesState[0].data).toEqualTypeOf<string | undefined>()
})
})

// Fix #7270
it('should have proper type inference with different options provided', () => {
const numbers = [1, 2, 3]
const queryKey = (n: number) => [n]
const queryFn = (n: number) => () => Promise.resolve(n)
const select = (data: number) => data.toString()

const queries = numbers.map((n) => ({
queryKey: [n],
queryFn: () => Promise.resolve(n),
select: (data: number) => data.toString(),
}))

const queriesWithoutSelect = numbers.map((n) => ({
queryKey: queryKey(n),
queryFn: queryFn(n),
}))

const queriesWithQueryOptions = numbers.map((n) =>
queryOptions({
queryKey: queryKey(n),
queryFn: queryFn(n),
select,
}),
)

const queriesWithQueryOptionsWithoutSelect = numbers.map((n) =>
queryOptions({
queryKey: queryKey(n),
queryFn: queryFn(n),
}),
)

const query1 = useQueries({ queries: queries })
expectTypeOf(query1.value).toEqualTypeOf<
Array<QueryObserverResult<string, Error>>
>()

const query2 = useQueries({ queries: queriesWithoutSelect })
expectTypeOf(query2.value).toEqualTypeOf<
Array<QueryObserverResult<number, Error>>
>()

const query3 = useQueries({ queries: queriesWithQueryOptions })
expectTypeOf(query3.value).toEqualTypeOf<
Array<QueryObserverResult<string, Error>>
>()

const query4 = useQueries({ queries: queriesWithQueryOptionsWithoutSelect })
expectTypeOf(query4.value).toEqualTypeOf<
Array<QueryObserverResult<number, Error>>
>()

const queryCombine = useQueries({
queries: queries,
combine: (data) => {
return data.reduce((acc, i) => {
acc.push(i.data ?? '')
return acc
}, [] as Array<string>)
},
})
expectTypeOf(queryCombine.value).toEqualTypeOf<Array<string>>()

const queryCombineWithoutSelect = useQueries({
queries: queriesWithoutSelect,
combine: (data) => {
return data.reduce((acc, i) => {
acc.push(i.data ?? 0)
return acc
}, [] as Array<number>)
},
})

expectTypeOf(queryCombineWithoutSelect.value).toEqualTypeOf<Array<number>>()
})
})
65 changes: 41 additions & 24 deletions packages/vue-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import type {
DefaultError,
DefinedQueryObserverResult,
QueriesObserverOptions,
QueriesPlaceholderDataFunction,
QueryFunction,
QueryKey,
QueryObserverResult,
ThrowOnError,
} from '@tanstack/query-core'
import type { UseQueryOptions } from './useQuery'
import type { QueryClient } from './queryClient'
import type { DeepUnwrapRef, DistributiveOmit, MaybeRefDeep } from './types'
import type { DeepUnwrapRef, MaybeRefDeep } from './types'

// This defines the `UseQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function does not have a parameter
Expand All @@ -32,20 +31,15 @@ type UseQueryOptionsForUseQueries<
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> = DistributiveOmit<
UseQueryOptions<TQueryFnData, TError, TData, unknown, TQueryKey>,
'placeholderData'
> & {
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData>
}
> = UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>

// Avoid TS depth-limit error in case of large array literal
type MAXIMUM_DEPTH = 20

// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
type SkipTokenForUseQueries = symbol

type GetOptions<T> =
type GetUseQueryOptionsForUseQueries<T> =
// Part 1: if UseQueryOptions are already being sent through, then just return T
T extends UseQueryOptions
? DeepUnwrapRef<T>
Expand Down Expand Up @@ -81,8 +75,20 @@ type GetOptions<T> =
unknown extends TData ? TQueryFnData : TData,
TQueryKey
>
: // Fallback
UseQueryOptionsForUseQueries
: T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, infer TQueryKey>
| SkipTokenForUseQueries
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseQueryOptionsForUseQueries<
TQueryFnData,
TError,
TQueryFnData,
TQueryKey
>
: // Fallback
UseQueryOptionsForUseQueries

// A defined initialData setting should return a DefinedQueryObserverResult rather than QueryObserverResult
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
Expand All @@ -101,7 +107,7 @@ type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
: QueryObserverResult<TData, TError>
: QueryObserverResult<TData, TError>

type GetResults<T> =
type GetUseQueryResult<T> =
// Part 1: if using UseQueryOptions then the types are already set
T extends UseQueryOptions<
infer TQueryFnData,
Expand Down Expand Up @@ -142,26 +148,37 @@ type GetResults<T> =
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
: // Fallback
QueryObserverResult
: T extends {
queryFn?:
| QueryFunction<infer TQueryFnData, any>
| SkipTokenForUseQueries
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? GetDefinedOrUndefinedQueryResult<
T,
TQueryFnData,
unknown extends TError ? DefaultError : TError
>
: // Fallback
QueryObserverResult

/**
* UseQueriesOptions reducer recursively unwraps function arguments to infer/enforce type param
*/
export type UseQueriesOptions<
T extends Array<any>,
TResult extends Array<any> = [],
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<UseQueryOptionsForUseQueries>
: T extends []
? []
: T extends [infer Head]
? [...TResult, GetOptions<Head>]
: T extends [infer Head, ...infer Tail]
? [...TResults, GetUseQueryOptionsForUseQueries<Head>]
: T extends [infer Head, ...infer Tails]
? UseQueriesOptions<
[...Tail],
[...TResult, GetOptions<Head>],
[...Tails],
[...TResults, GetUseQueryOptionsForUseQueries<Head>],
[...TDepth, 1]
>
: ReadonlyArray<unknown> extends T
Expand Down Expand Up @@ -192,18 +209,18 @@ export type UseQueriesOptions<
*/
export type UseQueriesResults<
T extends Array<any>,
TResult extends Array<any> = [],
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<QueryObserverResult>
: T extends []
? []
: T extends [infer Head]
? [...TResult, GetResults<Head>]
: T extends [infer Head, ...infer Tail]
? [...TResults, GetUseQueryResult<Head>]
: T extends [infer Head, ...infer Tails]
? UseQueriesResults<
[...Tail],
[...TResult, GetResults<Head>],
[...Tails],
[...TResults, GetUseQueryResult<Head>],
[...TDepth, 1]
>
: T extends Array<
Expand Down
Loading