Skip to content

Commit 4083c1e

Browse files
authored
fix(useQuery): make sure queryKeys are always an array at runtime when passed to the queryFn (TanStack#2200)
1 parent 9686426 commit 4083c1e

File tree

4 files changed

+34
-14
lines changed

4 files changed

+34
-14
lines changed

src/core/query.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import {
55
noop,
66
replaceEqualDeep,
77
timeUntilStale,
8+
ensureQueryKeyArray,
89
} from './utils'
910
import type {
1011
InitialDataFunction,
1112
QueryKey,
1213
QueryOptions,
1314
QueryStatus,
1415
QueryFunctionContext,
16+
EnsuredQueryKey,
1517
} from './types'
1618
import type { QueryCache } from './queryCache'
1719
import type { QueryObserver } from './queryObserver'
@@ -58,8 +60,8 @@ export interface FetchContext<
5860
> {
5961
fetchFn: () => unknown | Promise<unknown>
6062
fetchOptions?: FetchOptions
61-
options: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
62-
queryKey: TQueryKey
63+
options: QueryOptions<TQueryFnData, TError, TData, any>
64+
queryKey: EnsuredQueryKey<TQueryKey>
6365
state: QueryState<TData, TError>
6466
}
6567

@@ -380,9 +382,11 @@ export class Query<
380382
}
381383
}
382384

385+
const queryKey = ensureQueryKeyArray(this.queryKey)
386+
383387
// Create query function context
384388
const queryFnContext: QueryFunctionContext<TQueryKey> = {
385-
queryKey: this.queryKey,
389+
queryKey,
386390
pageParam: undefined,
387391
}
388392

@@ -393,10 +397,10 @@ export class Query<
393397
: Promise.reject('Missing queryFn')
394398

395399
// Trigger behavior hook
396-
const context: FetchContext<TQueryFnData, TError, TData, any> = {
400+
const context: FetchContext<TQueryFnData, TError, TData, TQueryKey> = {
397401
fetchOptions,
398402
options: this.options,
399-
queryKey: this.queryKey,
403+
queryKey: queryKey,
400404
state: this.state,
401405
fetchFn,
402406
}

src/core/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { RetryValue, RetryDelayValue } from './retryer'
44
import type { QueryFilters } from './utils'
55

66
export type QueryKey = string | readonly unknown[]
7+
export type EnsuredQueryKey<T extends QueryKey> = T extends string
8+
? [T]
9+
: Exclude<T, string>
710

811
export type QueryFunction<
912
T = unknown,
@@ -14,14 +17,12 @@ export interface QueryFunctionContext<
1417
TQueryKey extends QueryKey = QueryKey,
1518
TPageParam = any
1619
> {
17-
queryKey: TQueryKey
20+
queryKey: EnsuredQueryKey<TQueryKey>
1821
pageParam?: TPageParam
1922
}
2023

2124
export type InitialDataFunction<T> = () => T | undefined
2225

23-
export type InitialStaleFunction = () => boolean
24-
2526
export type PlaceholderDataFunction<TResult> = () => TResult | undefined
2627

2728
export type QueryKeyHashFunction<TQueryKey extends QueryKey> = (

src/core/utils.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Mutation } from './mutation'
22
import type { Query } from './query'
3+
import { EnsuredQueryKey } from './types'
34
import type {
45
MutationFunction,
56
MutationKey,
@@ -88,8 +89,12 @@ export function isValidTimeout(value: any): value is number {
8889
return typeof value === 'number' && value >= 0 && value !== Infinity
8990
}
9091

91-
export function ensureArray<T>(value: T | T[]): T[] {
92-
return Array.isArray(value) ? value : [value]
92+
export function ensureQueryKeyArray<T extends QueryKey>(
93+
value: T
94+
): EnsuredQueryKey<T> {
95+
return (Array.isArray(value)
96+
? value
97+
: ([value] as unknown)) as EnsuredQueryKey<T>
9398
}
9499

95100
export function difference<T>(array1: T[], array2: T[]): T[] {
@@ -256,7 +261,7 @@ export function hashQueryKeyByOptions<TQueryKey extends QueryKey = QueryKey>(
256261
* Default query keys hash function.
257262
*/
258263
export function hashQueryKey(queryKey: QueryKey): string {
259-
const asArray = Array.isArray(queryKey) ? queryKey : [queryKey]
264+
const asArray = ensureQueryKeyArray(queryKey)
260265
return stableValueHash(asArray)
261266
}
262267

@@ -280,7 +285,7 @@ export function stableValueHash(value: any): string {
280285
* Checks if key `b` partially matches with key `a`.
281286
*/
282287
export function partialMatchKey(a: QueryKey, b: QueryKey): boolean {
283-
return partialDeepEqual(ensureArray(a), ensureArray(b))
288+
return partialDeepEqual(ensureQueryKeyArray(a), ensureQueryKeyArray(b))
284289
}
285290

286291
/**

src/react/tests/useQuery.test.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,25 @@ describe('useQuery', () => {
7979
type MyData = number
8080
type MyQueryKey = readonly ['my-data', number]
8181

82-
const getMyData: QueryFunction<MyData, MyQueryKey> = async ({
82+
const getMyDataArrayKey: QueryFunction<MyData, MyQueryKey> = async ({
8383
queryKey: [, n],
8484
}) => {
8585
return n + 42
8686
}
8787

8888
useQuery({
8989
queryKey: ['my-data', 100],
90-
queryFn: getMyData,
90+
queryFn: getMyDataArrayKey,
91+
})
92+
93+
const getMyDataStringKey: QueryFunction<MyData, '1'> = async context => {
94+
expectType<['1']>(context.queryKey)
95+
return Number(context.queryKey[0]) + 42
96+
}
97+
98+
useQuery({
99+
queryKey: '1',
100+
queryFn: getMyDataStringKey,
91101
})
92102
}
93103
})

0 commit comments

Comments
 (0)