Skip to content

Commit 29e80d9

Browse files
fix(vue-query): fix type of queryOptions to allow plain properies or getters (#10452)
1 parent b97513f commit 29e80d9

File tree

9 files changed

+186
-20
lines changed

9 files changed

+186
-20
lines changed

.changeset/three-pillows-enter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/vue-query': patch
3+
---
4+
5+
fix(vue-query): fix type of queryOptions to allow plain properies or getters

packages/vue-query/src/__tests__/queryOptions.test-d.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ describe('queryOptions', () => {
1111
const key = queryKey()
1212
assertType(
1313
queryOptions({
14+
// @ts-expect-error this is a good error, because stallTime does not exist!
1415
queryKey: key,
1516
queryFn: () => Promise.resolve(5),
16-
// @ts-expect-error this is a good error, because stallTime does not exist!
1717
stallTime: 1000,
1818
}),
1919
)
@@ -119,11 +119,13 @@ describe('queryOptions', () => {
119119

120120
expectTypeOf(data).toEqualTypeOf<number | undefined>()
121121
})
122-
it('should allow to be passed to QueryClient methods while containing ref in queryKey', () => {
123-
const options = queryOptions({
124-
queryKey: ['key', ref(1), { nested: ref(2) }],
122+
it('should allow to be passed to QueryClient methods while containing getter', () => {
123+
const ref1 = ref(1)
124+
const ref2 = ref(2)
125+
const options = queryOptions(() => ({
126+
queryKey: ['key', ref1.value, { nested: ref2.value }],
125127
queryFn: () => Promise.resolve(5),
126-
})
128+
}))
127129

128130
const queryClient = new QueryClient()
129131

@@ -228,4 +230,27 @@ describe('queryOptions', () => {
228230

229231
expectTypeOf(data).toEqualTypeOf<number>()
230232
})
233+
234+
it('should allow accessing queryFn and other properties on the returned options object', () => {
235+
const options = queryOptions({
236+
queryKey: ['groups'],
237+
queryFn: () => Promise.resolve([]),
238+
})
239+
240+
expectTypeOf(options.queryFn).not.toBeUndefined()
241+
expectTypeOf(options.queryKey).not.toBeUndefined()
242+
expectTypeOf(options.staleTime).not.toBeUndefined()
243+
})
244+
245+
it('should allow accessing queryFn and other properties on the returned options when used with getter', () => {
246+
const options = queryOptions(() => ({
247+
queryKey: ['groups'],
248+
queryFn: () => Promise.resolve([]),
249+
}))
250+
251+
const resolvedGetter = options()
252+
253+
expectTypeOf(resolvedGetter.queryFn).not.toBeUndefined()
254+
expectTypeOf(resolvedGetter.queryKey).not.toBeUndefined()
255+
})
231256
})
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { describe, expect, it } from 'vitest'
22
import { queryOptions } from '../queryOptions'
3-
import type { UseQueryOptions } from '../useQuery'
3+
import type { QueryOptions } from '../queryOptions'
44

55
describe('queryOptions', () => {
66
it('should return the object received as a parameter without any modification.', () => {
7-
const object: UseQueryOptions = {
7+
const object: QueryOptions = {
88
queryKey: ['key'],
99
queryFn: () => Promise.resolve(5),
1010
} as const
1111

12-
expect(queryOptions(object)).toBe(object)
12+
const options = queryOptions(object)
13+
expect(options).toBe(object)
1314
})
1415
})

packages/vue-query/src/__tests__/useQueries.test-d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,27 @@ describe('UseQueries config object overload', () => {
263263
number | boolean | undefined
264264
>()
265265
})
266+
267+
it('should infer correct data type from queryOptions without initialData in useQueries', () => {
268+
const options = queryOptions({
269+
queryKey: ['key'],
270+
queryFn: () => Promise.resolve(5),
271+
})
272+
273+
const { value: queriesState } = useQueries({ queries: [options] })
274+
275+
expectTypeOf(queriesState[0].data).toEqualTypeOf<number | undefined>()
276+
})
277+
278+
it('should infer correct data type from queryOptions with select in useQueries', () => {
279+
const options = queryOptions({
280+
queryKey: ['key'],
281+
queryFn: () => Promise.resolve(5),
282+
select: (data) => data.toString(),
283+
})
284+
285+
const { value: queriesState } = useQueries({ queries: [options] })
286+
287+
expectTypeOf(queriesState[0].data).toEqualTypeOf<string | undefined>()
288+
})
266289
})

packages/vue-query/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { VueQueryPlugin } from './vueQueryPlugin'
66
export { QueryClient } from './queryClient'
77
export { QueryCache } from './queryCache'
88
export { queryOptions } from './queryOptions'
9+
export { type QueryOptions } from './queryOptions'
910
export { infiniteQueryOptions } from './infiniteQueryOptions'
1011
export type {
1112
DefinedInitialDataInfiniteOptions,

packages/vue-query/src/queryClient.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,15 +202,23 @@ export class QueryClient extends QC {
202202
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
203203
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
204204
>(
205-
filters?: InvalidateQueryFilters<TTaggedQueryKey>,
205+
filters?:
206+
| InvalidateQueryFilters<TTaggedQueryKey>
207+
| (() => InvalidateQueryFilters<TTaggedQueryKey>),
206208
options?: MaybeRefDeep<InvalidateOptions>,
207209
): Promise<void>
208210
invalidateQueries<TTaggedQueryKey extends QueryKey = QueryKey>(
209-
filters: MaybeRefDeep<InvalidateQueryFilters<TTaggedQueryKey>> = {},
210-
options: MaybeRefDeep<InvalidateOptions> = {},
211+
filters:
212+
| MaybeRefDeep<InvalidateQueryFilters<TTaggedQueryKey>>
213+
| (() => InvalidateQueryFilters<TTaggedQueryKey>) = {},
214+
options: MaybeRefDeep<InvalidateOptions> | (() => InvalidateOptions) = {},
211215
): Promise<void> {
212-
const filtersCloned = cloneDeepUnref(filters)
213-
const optionsCloned = cloneDeepUnref(options)
216+
const filtersCloned = cloneDeepUnref(
217+
filters as MaybeRefDeep<InvalidateQueryFilters<TTaggedQueryKey>>,
218+
)
219+
const optionsCloned = cloneDeepUnref(
220+
options as MaybeRefDeep<InvalidateOptions>,
221+
)
214222

215223
super.invalidateQueries(
216224
{ ...filtersCloned, refetchType: 'none' },
@@ -275,9 +283,17 @@ export class QueryClient extends QC {
275283
TQueryKey extends QueryKey = QueryKey,
276284
TPageParam = never,
277285
>(
278-
options: MaybeRefDeep<
279-
FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
280-
>,
286+
options:
287+
| MaybeRefDeep<
288+
FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
289+
>
290+
| (() => FetchQueryOptions<
291+
TQueryFnData,
292+
TError,
293+
TData,
294+
TQueryKey,
295+
TPageParam
296+
>),
281297
): Promise<TData>
282298
fetchQuery<
283299
TQueryFnData,

packages/vue-query/src/queryOptions.ts

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,60 @@
1-
import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core'
1+
import type { DeepUnwrapRef, ShallowOption } from './types'
22
import type {
3-
DefinedInitialQueryOptions,
4-
UndefinedInitialQueryOptions,
5-
} from './useQuery'
3+
DataTag,
4+
DefaultError,
5+
Enabled,
6+
InitialDataFunction,
7+
NonUndefinedGuard,
8+
QueryKey,
9+
QueryObserverOptions,
10+
} from '@tanstack/query-core'
11+
12+
export type QueryOptions<
13+
TQueryFnData = unknown,
14+
TError = DefaultError,
15+
TData = TQueryFnData,
16+
TQueryData = TQueryFnData,
17+
TQueryKey extends QueryKey = QueryKey,
18+
> = {
19+
[Property in keyof QueryObserverOptions<
20+
TQueryFnData,
21+
TError,
22+
TData,
23+
TQueryData,
24+
TQueryKey
25+
>]: Property extends 'enabled'
26+
? () => Enabled<TQueryFnData, TError, TQueryData, DeepUnwrapRef<TQueryKey>>
27+
: QueryObserverOptions<
28+
TQueryFnData,
29+
TError,
30+
TData,
31+
TQueryData,
32+
DeepUnwrapRef<TQueryKey>
33+
>[Property]
34+
} & ShallowOption
35+
36+
export type UndefinedInitialQueryOptions<
37+
TQueryFnData = unknown,
38+
TError = DefaultError,
39+
TData = TQueryFnData,
40+
TQueryKey extends QueryKey = QueryKey,
41+
> = QueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey> & {
42+
initialData?:
43+
| undefined
44+
| InitialDataFunction<NonUndefinedGuard<TQueryFnData>>
45+
| NonUndefinedGuard<TQueryFnData>
46+
}
47+
48+
export type DefinedInitialQueryOptions<
49+
TQueryFnData = unknown,
50+
TError = DefaultError,
51+
TData = TQueryFnData,
52+
TQueryKey extends QueryKey = QueryKey,
53+
> = QueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey> & {
54+
initialData:
55+
| NonUndefinedGuard<TQueryFnData>
56+
| (() => NonUndefinedGuard<TQueryFnData>)
57+
}
658

759
export function queryOptions<
860
TQueryFnData = unknown,
@@ -15,6 +67,22 @@ export function queryOptions<
1567
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
1668
}
1769

70+
export function queryOptions<
71+
TQueryFnData = unknown,
72+
TError = DefaultError,
73+
TData = TQueryFnData,
74+
TQueryKey extends QueryKey = QueryKey,
75+
>(
76+
options: () => DefinedInitialQueryOptions<
77+
TQueryFnData,
78+
TError,
79+
TData,
80+
TQueryKey
81+
>,
82+
): () => DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
83+
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
84+
}
85+
1886
export function queryOptions<
1987
TQueryFnData = unknown,
2088
TError = DefaultError,
@@ -26,6 +94,27 @@ export function queryOptions<
2694
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
2795
}
2896

97+
export function queryOptions<
98+
TQueryFnData = unknown,
99+
TError = DefaultError,
100+
TData = TQueryFnData,
101+
TQueryKey extends QueryKey = QueryKey,
102+
>(
103+
options: () => UndefinedInitialQueryOptions<
104+
TQueryFnData,
105+
TError,
106+
TData,
107+
TQueryKey
108+
>,
109+
): () => UndefinedInitialQueryOptions<
110+
TQueryFnData,
111+
TError,
112+
TData,
113+
TQueryKey
114+
> & {
115+
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
116+
}
117+
29118
export function queryOptions(options: unknown) {
30119
return options
31120
}

packages/vue-query/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type UnwrapLeaf =
2222
| Set<any>
2323
| WeakSet<any>
2424

25+
export type MaybeGetter<T> = T | (() => T)
26+
2527
export type MaybeRef<T> = Ref<T> | ComputedRef<T> | T
2628

2729
export type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T)

packages/vue-query/src/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,7 @@ function isPlainObject(value: unknown): value is Object {
109109
function isFunction(value: unknown): value is Function {
110110
return typeof value === 'function'
111111
}
112+
113+
export function toValueDeep<T>(source: (() => T) | MaybeRefDeep<T>): T {
114+
return isFunction(source) ? source() : cloneDeepUnref(source)
115+
}

0 commit comments

Comments
 (0)