Skip to content

Commit 175757a

Browse files
authored
fix(types): Fix type errors when using fully-typed inputs like QueryFilters<Data, Error>, and test all QueryClient methods to detect similar issues (#8375)
* Add test with failing types * Tidy up * Move tests to correct file * Add initial fixes (some review and testing needed) * Formatting * Ensure T params are mandatory for new overloads * Fix type issues * Unnecessary DataTag * Add typeless tests * Fix some cases * Fixing more cases * Fix some typings * Fix remaining issues * Fix linting errors * Fix one warning * Revert unnecessary change to getQueryDefaults * Remove mutation defaults todo * Rename var * Remove all additional overloads in favour of a single definition which supports all cases * Change type tests to more idomatic api
1 parent ceb4094 commit 175757a

File tree

4 files changed

+437
-47
lines changed

4 files changed

+437
-47
lines changed

packages/query-core/src/__tests__/queryClient.test-d.tsx

Lines changed: 319 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { describe, expectTypeOf, it } from 'vitest'
22
import { QueryClient } from '../queryClient'
3-
import type { QueryState } from '../query'
4-
import type { DataTag, InfiniteData, QueryKey } from '../types'
3+
import type { MutationFilters, QueryFilters, Updater } from '../utils'
4+
import type { Mutation } from '../mutation'
5+
import type { Query, QueryState } from '../query'
6+
import type {
7+
DataTag,
8+
DefaultError,
9+
DefaultedQueryObserverOptions,
10+
EnsureQueryDataOptions,
11+
FetchInfiniteQueryOptions,
12+
InfiniteData,
13+
MutationOptions,
14+
OmitKeyof,
15+
QueryKey,
16+
QueryObserverOptions,
17+
} from '../types'
518

619
describe('getQueryData', () => {
720
it('should be typed if key is tagged', () => {
@@ -184,3 +197,307 @@ describe('defaultOptions', () => {
184197
})
185198
})
186199
})
200+
201+
describe('fully typed usage', () => {
202+
it('type-checks various methods with data & error included in the type', async () => {
203+
const queryClient = new QueryClient()
204+
205+
type TData = { foo: string }
206+
type TError = DefaultError & { bar: string }
207+
208+
//
209+
// Construct typed arguments
210+
//
211+
212+
const queryOptions: EnsureQueryDataOptions<TData, TError> = {
213+
queryKey: ['key'] as any,
214+
}
215+
const fetchInfiniteQueryOptions: FetchInfiniteQueryOptions<TData, TError> =
216+
{
217+
queryKey: ['key'] as any,
218+
pages: 5,
219+
getNextPageParam: (lastPage) => {
220+
expectTypeOf(lastPage).toEqualTypeOf<TData>()
221+
return 0
222+
},
223+
initialPageParam: 0,
224+
}
225+
const mutationOptions: MutationOptions<TData, TError> = {}
226+
227+
const queryFilters: QueryFilters<
228+
TData,
229+
TError,
230+
TData,
231+
QueryKey & DataTag<unknown, TData, TError>
232+
> = {
233+
predicate(query) {
234+
expectTypeOf(query).toEqualTypeOf<
235+
Query<
236+
TData,
237+
TError,
238+
TData,
239+
QueryKey & DataTag<unknown, TData, TError>
240+
>
241+
>()
242+
expectTypeOf(query.state.data).toEqualTypeOf<TData | undefined>()
243+
expectTypeOf(query.state.error).toEqualTypeOf<TError | null>()
244+
return false
245+
},
246+
}
247+
const queryKey = queryFilters.queryKey!
248+
249+
const mutationFilters: MutationFilters<TData, TError> = {
250+
predicate(mutation) {
251+
expectTypeOf(mutation).toEqualTypeOf<Mutation<TData, TError>>()
252+
expectTypeOf(mutation.state.data).toEqualTypeOf<TData | undefined>()
253+
expectTypeOf(mutation.state.error).toEqualTypeOf<TError | null>()
254+
return false
255+
},
256+
}
257+
const mutationKey = mutationOptions.mutationKey!
258+
259+
//
260+
// Method type tests
261+
//
262+
263+
const state = queryClient.getQueryState(queryKey)
264+
expectTypeOf(state).toEqualTypeOf<QueryState<TData, TError> | undefined>()
265+
266+
const queryData1 = queryClient.getQueryData(queryKey)
267+
expectTypeOf(queryData1).toEqualTypeOf<TData | undefined>()
268+
269+
const queryData2 = await queryClient.ensureQueryData(queryOptions)
270+
expectTypeOf(queryData2).toEqualTypeOf<TData>()
271+
272+
const queriesData = queryClient.getQueriesData(queryFilters)
273+
expectTypeOf(queriesData).toEqualTypeOf<
274+
Array<[QueryKey, TData | undefined]>
275+
>()
276+
277+
const queryData3 = queryClient.setQueryData(queryKey, { foo: '' })
278+
type SetQueryDataUpdaterArg = Parameters<
279+
typeof queryClient.setQueryData<unknown, typeof queryKey>
280+
>[1]
281+
282+
expectTypeOf<SetQueryDataUpdaterArg>().toEqualTypeOf<
283+
Updater<TData | undefined, TData | undefined>
284+
>()
285+
expectTypeOf(queryData3).toEqualTypeOf<TData | undefined>()
286+
287+
const queriesData2 = queryClient.setQueriesData(queryFilters, { foo: '' }) // TODO: types here are wrong and coming up undefined
288+
type SetQueriesDataUpdaterArg = Parameters<
289+
typeof queryClient.setQueriesData<unknown, typeof queryFilters>
290+
>[1]
291+
292+
expectTypeOf<SetQueriesDataUpdaterArg>().toEqualTypeOf<
293+
Updater<TData | undefined, TData | undefined>
294+
>()
295+
expectTypeOf(queriesData2).toEqualTypeOf<
296+
Array<[QueryKey, TData | undefined]>
297+
>()
298+
299+
const queryState = queryClient.getQueryState(queryKey)
300+
expectTypeOf(queryState).toEqualTypeOf<
301+
QueryState<TData, TError> | undefined
302+
>()
303+
304+
const fetchedQuery = await queryClient.fetchQuery(queryOptions)
305+
expectTypeOf(fetchedQuery).toEqualTypeOf<TData>()
306+
307+
queryClient.prefetchQuery(queryOptions)
308+
309+
const infiniteQuery = await queryClient.fetchInfiniteQuery(
310+
fetchInfiniteQueryOptions,
311+
)
312+
expectTypeOf(infiniteQuery).toEqualTypeOf<InfiniteData<TData, unknown>>()
313+
314+
const infiniteQueryData = await queryClient.ensureInfiniteQueryData(
315+
fetchInfiniteQueryOptions,
316+
)
317+
expectTypeOf(infiniteQueryData).toEqualTypeOf<
318+
InfiniteData<TData, unknown>
319+
>()
320+
321+
const defaultQueryOptions = queryClient.defaultQueryOptions(queryOptions)
322+
expectTypeOf(defaultQueryOptions).toEqualTypeOf<
323+
DefaultedQueryObserverOptions<TData, TError, TData, TData, QueryKey>
324+
>()
325+
326+
const mutationOptions2 = queryClient.defaultMutationOptions(mutationOptions)
327+
expectTypeOf(mutationOptions2).toEqualTypeOf<
328+
MutationOptions<TData, TError, void, unknown>
329+
>()
330+
331+
queryClient.setMutationDefaults(mutationKey, {
332+
onSettled(data, error, variables, context) {
333+
expectTypeOf(data).toEqualTypeOf<unknown>()
334+
expectTypeOf(error).toEqualTypeOf<DefaultError | null>()
335+
expectTypeOf(variables).toEqualTypeOf<void>()
336+
expectTypeOf(context).toEqualTypeOf<unknown>()
337+
},
338+
})
339+
340+
const queryDefaults = queryClient.getQueryDefaults(queryKey)
341+
expectTypeOf(queryDefaults).toEqualTypeOf<
342+
OmitKeyof<QueryObserverOptions<any, any, any, any, any>, 'queryKey'>
343+
>()
344+
345+
// Voids and Untyped returns
346+
queryClient.invalidateQueries(queryFilters)
347+
queryClient.isFetching(queryFilters)
348+
queryClient.isMutating(mutationFilters)
349+
queryClient.removeQueries(queryFilters)
350+
queryClient.resetQueries(queryFilters)
351+
queryClient.cancelQueries(queryFilters)
352+
queryClient.invalidateQueries(queryFilters)
353+
queryClient.refetchQueries(queryFilters)
354+
queryClient.prefetchInfiniteQuery(fetchInfiniteQueryOptions)
355+
queryClient.setQueryDefaults(queryKey, {} as any)
356+
queryClient.getMutationDefaults(mutationKey)
357+
})
358+
359+
it('type-checks various methods with untyped arguments', async () => {
360+
const queryClient = new QueryClient()
361+
362+
//
363+
// Construct typed arguments
364+
//
365+
366+
const queryOptions: EnsureQueryDataOptions = {
367+
queryKey: ['key'] as any,
368+
}
369+
const fetchInfiniteQueryOptions: FetchInfiniteQueryOptions = {
370+
queryKey: ['key'] as any,
371+
pages: 5,
372+
getNextPageParam: (lastPage) => {
373+
expectTypeOf(lastPage).toEqualTypeOf<unknown>()
374+
return 0
375+
},
376+
initialPageParam: 0,
377+
}
378+
const mutationOptions: MutationOptions = {}
379+
380+
const queryFilters: QueryFilters = {
381+
predicate(query) {
382+
expectTypeOf(query).toEqualTypeOf<Query<unknown, DefaultError>>()
383+
expectTypeOf(query.state.data).toEqualTypeOf<unknown>()
384+
expectTypeOf(query.state.error).toEqualTypeOf<DefaultError | null>()
385+
return false
386+
},
387+
}
388+
const queryKey = queryFilters.queryKey!
389+
390+
const mutationFilters: MutationFilters = {
391+
predicate(mutation) {
392+
expectTypeOf(mutation).toEqualTypeOf<Mutation>()
393+
expectTypeOf(mutation.state.data).toEqualTypeOf<unknown>()
394+
expectTypeOf(mutation.state.error).toEqualTypeOf<DefaultError | null>()
395+
return false
396+
},
397+
}
398+
const mutationKey = mutationOptions.mutationKey!
399+
400+
//
401+
// Method type tests
402+
//
403+
404+
const state = queryClient.getQueryState(queryKey)
405+
expectTypeOf(state).toEqualTypeOf<
406+
QueryState<unknown, DefaultError> | undefined
407+
>()
408+
409+
const queryData1 = queryClient.getQueryData(queryKey)
410+
expectTypeOf(queryData1).toEqualTypeOf<unknown>()
411+
412+
const queryData2 = await queryClient.ensureQueryData(queryOptions)
413+
expectTypeOf(queryData2).toEqualTypeOf<unknown>()
414+
415+
const queriesData = queryClient.getQueriesData(queryFilters)
416+
expectTypeOf(queriesData).toEqualTypeOf<Array<[QueryKey, unknown]>>()
417+
418+
const queryData3 = queryClient.setQueryData(queryKey, { foo: '' })
419+
type SetQueryDataUpdaterArg = Parameters<
420+
typeof queryClient.setQueryData<unknown, typeof queryKey>
421+
>[1]
422+
423+
expectTypeOf<SetQueryDataUpdaterArg>().toEqualTypeOf<
424+
Updater<unknown, unknown>
425+
>()
426+
expectTypeOf(queryData3).toEqualTypeOf<unknown>()
427+
428+
const queriesData2 = queryClient.setQueriesData(queryFilters, { foo: '' }) // TODO: types here are wrong and coming up undefined
429+
type SetQueriesDataUpdaterArg = Parameters<
430+
typeof queryClient.setQueriesData<unknown, typeof queryFilters>
431+
>[1]
432+
433+
expectTypeOf<SetQueriesDataUpdaterArg>().toEqualTypeOf<
434+
Updater<unknown, unknown>
435+
>()
436+
expectTypeOf(queriesData2).toEqualTypeOf<Array<[QueryKey, unknown]>>()
437+
438+
const queryState = queryClient.getQueryState(queryKey)
439+
expectTypeOf(queryState).toEqualTypeOf<
440+
QueryState<unknown, DefaultError> | undefined
441+
>()
442+
443+
const fetchedQuery = await queryClient.fetchQuery(queryOptions)
444+
expectTypeOf(fetchedQuery).toEqualTypeOf<unknown>()
445+
446+
queryClient.prefetchQuery(queryOptions)
447+
448+
const infiniteQuery = await queryClient.fetchInfiniteQuery(
449+
fetchInfiniteQueryOptions,
450+
)
451+
expectTypeOf(infiniteQuery).toEqualTypeOf<InfiniteData<unknown, unknown>>()
452+
453+
const infiniteQueryData = await queryClient.ensureInfiniteQueryData(
454+
fetchInfiniteQueryOptions,
455+
)
456+
expectTypeOf(infiniteQueryData).toEqualTypeOf<
457+
InfiniteData<unknown, unknown>
458+
>()
459+
460+
const defaultQueryOptions = queryClient.defaultQueryOptions(queryOptions)
461+
expectTypeOf(defaultQueryOptions).toEqualTypeOf<
462+
DefaultedQueryObserverOptions<
463+
unknown,
464+
DefaultError,
465+
unknown,
466+
unknown,
467+
QueryKey
468+
>
469+
>()
470+
471+
const mutationOptions2 = queryClient.defaultMutationOptions(mutationOptions)
472+
expectTypeOf(mutationOptions2).toEqualTypeOf<
473+
MutationOptions<unknown, DefaultError, void, unknown>
474+
>()
475+
476+
queryClient.setMutationDefaults(mutationKey, {
477+
onSettled(data, error, variables, context) {
478+
expectTypeOf(data).toEqualTypeOf<unknown>()
479+
expectTypeOf(error).toEqualTypeOf<DefaultError | null>()
480+
expectTypeOf(variables).toEqualTypeOf<void>()
481+
expectTypeOf(context).toEqualTypeOf<unknown>()
482+
},
483+
})
484+
485+
const queryDefaults = queryClient.getQueryDefaults(queryKey)
486+
expectTypeOf(queryDefaults).toEqualTypeOf<
487+
OmitKeyof<QueryObserverOptions<any, any, any, any, any>, 'queryKey'>
488+
>()
489+
490+
// Voids and Untyped returns
491+
queryClient.invalidateQueries(queryFilters)
492+
queryClient.isFetching(queryFilters)
493+
queryClient.isMutating(mutationFilters)
494+
queryClient.removeQueries(queryFilters)
495+
queryClient.resetQueries(queryFilters)
496+
queryClient.cancelQueries(queryFilters)
497+
queryClient.invalidateQueries(queryFilters)
498+
queryClient.refetchQueries(queryFilters)
499+
queryClient.prefetchInfiniteQuery(fetchInfiniteQueryOptions)
500+
queryClient.setQueryDefaults(queryKey, {} as any)
501+
queryClient.getMutationDefaults(mutationKey)
502+
})
503+
})

packages/query-core/src/__tests__/utils.test-d.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { describe, expectTypeOf, it } from 'vitest'
22
import { QueryClient } from '../queryClient'
33
import type { QueryFilters } from '../utils'
4-
import type { DataTag } from '../types'
4+
import type { DataTag, QueryKey } from '../types'
55

66
describe('QueryFilters', () => {
77
it('should be typed if generics are passed', () => {
88
type TData = { a: number; b: string }
99

10-
const a: QueryFilters<TData> = {
10+
const filters: QueryFilters<
11+
TData,
12+
Error,
13+
TData,
14+
QueryKey & DataTag<unknown, TData>
15+
> = {
1116
predicate(query) {
1217
expectTypeOf(query.setData({ a: 1, b: '1' })).toEqualTypeOf<TData>()
1318
return true
@@ -17,18 +22,23 @@ describe('QueryFilters', () => {
1722

1823
const queryClient = new QueryClient()
1924

20-
const data = queryClient.getQueryData(a.queryKey!)
25+
const data = queryClient.getQueryData(filters.queryKey!)
2126
expectTypeOf(data).toEqualTypeOf<TData | undefined>()
2227

23-
const error = queryClient.getQueryState(a.queryKey!)?.error
28+
const error = queryClient.getQueryState(filters.queryKey!)?.error
2429
expectTypeOf(error).toEqualTypeOf<Error | null | undefined>()
2530
})
2631

2732
it('should be typed if generics are passed including an error type', () => {
2833
type TData = { a: number; b: string }
2934
type TError = Error & { message: string }
3035

31-
const a: QueryFilters<TData, TError> = {
36+
const filters: QueryFilters<
37+
TData,
38+
TError,
39+
TData,
40+
QueryKey & DataTag<unknown, TData, TError>
41+
> = {
3242
predicate(query) {
3343
expectTypeOf(query.setData({ a: 1, b: '1' })).toEqualTypeOf<TData>()
3444
return true
@@ -38,10 +48,10 @@ describe('QueryFilters', () => {
3848

3949
const queryClient = new QueryClient()
4050

41-
const data = queryClient.getQueryData(a.queryKey!)
51+
const data = queryClient.getQueryData(filters.queryKey!)
4252
expectTypeOf(data).toEqualTypeOf<TData | undefined>()
4353

44-
const error = queryClient.getQueryState(a.queryKey!)?.error
54+
const error = queryClient.getQueryState(filters.queryKey!)?.error
4555
expectTypeOf(error).toEqualTypeOf<TError | null | undefined>()
4656
})
4757

0 commit comments

Comments
 (0)