Skip to content

Commit b54936f

Browse files
fix(useQueries): add type tests for useQueries and fix a couple of type bugs (#6471)
Creating new useQueries type tests in react-query and vue-query and fixing a couple of type bugs.
1 parent 23374bb commit b54936f

4 files changed

Lines changed: 436 additions & 73 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { describe, it } from 'vitest'
2+
import { queryOptions, useQueries } from '..'
3+
import { doNotExecute } from './utils'
4+
import type { UseQueryOptions } from '..'
5+
import type { Equal, Expect } from './utils'
6+
7+
describe('UseQueries config object overload', () => {
8+
it('TData should always be defined when initialData is provided as an object', () => {
9+
const query1 = {
10+
queryKey: ['key1'],
11+
queryFn: () => {
12+
return {
13+
wow: true,
14+
}
15+
},
16+
initialData: {
17+
wow: false,
18+
},
19+
}
20+
21+
const query2 = {
22+
queryKey: ['key2'],
23+
queryFn: () => 'Query Data',
24+
initialData: 'initial data',
25+
}
26+
27+
const query3 = {
28+
queryKey: ['key2'],
29+
queryFn: () => 'Query Data',
30+
}
31+
32+
doNotExecute(() => {
33+
const queryResults = useQueries({ queries: [query1, query2, query3] })
34+
35+
const query1Data = queryResults[0].data
36+
const query2Data = queryResults[1].data
37+
const query3Data = queryResults[2].data
38+
39+
const result1: Expect<Equal<{ wow: boolean }, typeof query1Data>> = true
40+
41+
const result2: Expect<Equal<string, typeof query2Data>> = true
42+
43+
const result3: Expect<Equal<string | undefined, typeof query3Data>> = true
44+
45+
return result1 && result2 && result3
46+
})
47+
})
48+
49+
it('TData should be defined when passed through queryOptions', () => {
50+
doNotExecute(() => {
51+
const options = queryOptions({
52+
queryKey: ['key'],
53+
queryFn: () => {
54+
return {
55+
wow: true,
56+
}
57+
},
58+
initialData: {
59+
wow: true,
60+
},
61+
})
62+
const queryResults = useQueries({ queries: [options] })
63+
64+
const data = queryResults[0].data
65+
66+
const result: Expect<Equal<{ wow: boolean }, typeof data>> = true
67+
return result
68+
})
69+
})
70+
71+
it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => {
72+
doNotExecute(() => {
73+
const query1 = queryOptions({
74+
queryKey: ['key'],
75+
queryFn: () => Promise.resolve(1),
76+
select: (data) => data > 1,
77+
})
78+
79+
const query2 = {
80+
queryKey: ['key'],
81+
queryFn: () => Promise.resolve(1),
82+
select: (data: number) => data > 1,
83+
}
84+
85+
const queryResults = useQueries({ queries: [query1, query2] })
86+
const query1Data = queryResults[0].data
87+
const query2Data = queryResults[1].data
88+
89+
const result1: Expect<Equal<boolean | undefined, typeof query1Data>> =
90+
true
91+
const result2: Expect<Equal<boolean | undefined, typeof query2Data>> =
92+
true
93+
return result1 && result2
94+
})
95+
})
96+
97+
it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => {
98+
doNotExecute(() => {
99+
const queryResults = useQueries({
100+
queries: [
101+
{
102+
queryKey: ['key'],
103+
queryFn: () => {
104+
return {
105+
wow: true,
106+
}
107+
},
108+
initialData: () => undefined as { wow: boolean } | undefined,
109+
},
110+
],
111+
})
112+
113+
const data = queryResults[0].data
114+
115+
const result: Expect<Equal<{ wow: boolean } | undefined, typeof data>> =
116+
true
117+
return result
118+
})
119+
})
120+
121+
describe('custom hook', () => {
122+
it('should allow custom hooks using UseQueryOptions', () => {
123+
doNotExecute(() => {
124+
type Data = string
125+
126+
const useCustomQueries = (
127+
options?: Omit<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
128+
) => {
129+
return useQueries({
130+
queries: [
131+
{
132+
...options,
133+
queryKey: ['todos-key'],
134+
queryFn: () => Promise.resolve('data'),
135+
},
136+
],
137+
})
138+
}
139+
140+
const queryResults = useCustomQueries()
141+
const data = queryResults[0].data
142+
143+
const result: Expect<Equal<Data | undefined, typeof data>> = true
144+
return result
145+
})
146+
})
147+
})
148+
})

‎packages/react-query/src/useQueries.ts‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import {
2020
shouldSuspend,
2121
willFetch,
2222
} from './suspense'
23-
import type { UseQueryOptions, UseQueryResult } from './types'
23+
import type {
24+
DefinedUseQueryResult,
25+
UseQueryOptions,
26+
UseQueryResult,
27+
} from './types'
2428
import type {
2529
DefaultError,
2630
QueriesObserverOptions,
@@ -95,36 +99,55 @@ type GetOptions<T> =
9599
: // Fallback
96100
UseQueryOptionsForUseQueries
97101

102+
// A defined initialData setting should return a DefinedUseQueryResult rather than UseQueryResult
103+
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
104+
initialData?: infer TInitialData
105+
}
106+
? unknown extends TInitialData
107+
? UseQueryResult<TData, TError>
108+
: TInitialData extends TData
109+
? DefinedUseQueryResult<TData, TError>
110+
: TInitialData extends () => infer TInitialDataResult
111+
? unknown extends TInitialDataResult
112+
? UseQueryResult<TData, TError>
113+
: TInitialDataResult extends TData
114+
? DefinedUseQueryResult<TData, TError>
115+
: UseQueryResult<TData, TError>
116+
: UseQueryResult<TData, TError>
117+
: UseQueryResult<TData, TError>
118+
98119
type GetResults<T> =
99120
// Part 1: responsible for mapping explicit type parameter to function result, if object
100121
T extends { queryFnData: any; error?: infer TError; data: infer TData }
101-
? UseQueryResult<TData, TError>
122+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
102123
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
103-
? UseQueryResult<TQueryFnData, TError>
124+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
104125
: T extends { data: infer TData; error?: infer TError }
105-
? UseQueryResult<TData, TError>
126+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
106127
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
107128
T extends [any, infer TError, infer TData]
108-
? UseQueryResult<TData, TError>
129+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
109130
: T extends [infer TQueryFnData, infer TError]
110-
? UseQueryResult<TQueryFnData, TError>
131+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
111132
: T extends [infer TQueryFnData]
112-
? UseQueryResult<TQueryFnData>
133+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
113134
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
114135
T extends {
115136
queryFn?: QueryFunction<infer TQueryFnData, any>
116137
select?: (data: any) => infer TData
117138
throwOnError?: ThrowOnError<any, infer TError, any, any>
118139
}
119-
? UseQueryResult<
140+
? GetDefinedOrUndefinedQueryResult<
141+
T,
120142
unknown extends TData ? TQueryFnData : TData,
121143
unknown extends TError ? DefaultError : TError
122144
>
123145
: T extends {
124146
queryFn?: QueryFunction<infer TQueryFnData, any>
125147
throwOnError?: ThrowOnError<any, infer TError, any, any>
126148
}
127-
? UseQueryResult<
149+
? GetDefinedOrUndefinedQueryResult<
150+
T,
128151
TQueryFnData,
129152
unknown extends TError ? DefaultError : TError
130153
>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { describe, it } from 'vitest'
2+
import { reactive } from 'vue'
3+
import { useQueries } from '..'
4+
import { queryOptions } from '../queryOptions'
5+
import { doNotExecute } from './test-utils'
6+
import type { UseQueryOptions } from '../useQuery'
7+
import type { Equal, Expect } from './test-utils'
8+
9+
describe('UseQueries config object overload', () => {
10+
it('TData should always be defined when initialData is provided as an object', () => {
11+
const query1 = {
12+
queryKey: ['key1'],
13+
queryFn: () => {
14+
return {
15+
wow: true,
16+
}
17+
},
18+
initialData: {
19+
wow: false,
20+
},
21+
}
22+
23+
const query2 = queryOptions({
24+
queryKey: ['key2'],
25+
queryFn: () => 'Query Data',
26+
initialData: 'initial data',
27+
})
28+
29+
const query3 = {
30+
queryKey: ['key2'],
31+
queryFn: () => 'Query Data',
32+
}
33+
34+
doNotExecute(() => {
35+
const { value: queriesState } = useQueries({
36+
queries: [query1, query2, query3],
37+
})
38+
39+
const query1Data = queriesState[0].data
40+
const query2Data = queriesState[1].data
41+
const query3Data = queriesState[2].data
42+
43+
const result1: Expect<Equal<{ wow: boolean }, typeof query1Data>> = true
44+
45+
const result2: Expect<Equal<string, typeof query2Data>> = true
46+
47+
const result3: Expect<Equal<string | undefined, typeof query3Data>> = true
48+
49+
return result1 && result2 && result3
50+
})
51+
})
52+
53+
it('TData should be defined when passed through queryOptions', () => {
54+
doNotExecute(() => {
55+
const options = queryOptions({
56+
queryKey: ['key'],
57+
queryFn: () => {
58+
return {
59+
wow: true,
60+
}
61+
},
62+
initialData: {
63+
wow: true,
64+
},
65+
})
66+
67+
const { value: queriesState } = useQueries({ queries: [options] })
68+
69+
const data = queriesState[0].data
70+
71+
const result: Expect<Equal<{ wow: boolean }, typeof data>> = true
72+
return result
73+
})
74+
})
75+
76+
it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQueries', () => {
77+
doNotExecute(() => {
78+
const query1 = queryOptions({
79+
queryKey: ['key'],
80+
queryFn: () => Promise.resolve(1),
81+
select: (data) => data > 1,
82+
})
83+
84+
const query2 = {
85+
queryKey: ['key'],
86+
queryFn: () => Promise.resolve(1),
87+
select: (data: any) => data > 1,
88+
}
89+
90+
const queriesState = reactive(useQueries({ queries: [query1, query2] }))
91+
const query1Data = queriesState.value[0].data
92+
const query2Data = queriesState.value[1].data
93+
94+
const result1: Expect<Equal<boolean | undefined, typeof query1Data>> =
95+
true
96+
const result2: Expect<Equal<boolean | undefined, typeof query2Data>> =
97+
true
98+
return result1 && result2
99+
})
100+
})
101+
102+
it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => {
103+
doNotExecute(() => {
104+
const { value: queriesState } = useQueries({
105+
queries: [
106+
{
107+
queryKey: ['key'],
108+
queryFn: () => {
109+
return {
110+
wow: true,
111+
}
112+
},
113+
initialData: () => undefined as { wow: boolean } | undefined,
114+
},
115+
],
116+
})
117+
118+
const data = queriesState[0].data
119+
120+
const result: Expect<Equal<{ wow: boolean } | undefined, typeof data>> =
121+
true
122+
return result
123+
})
124+
})
125+
126+
describe('custom hook', () => {
127+
it('should allow custom hooks using UseQueryOptions', () => {
128+
doNotExecute(() => {
129+
type Data = string
130+
131+
const useCustomQueries = (
132+
options?: Omit<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
133+
) => {
134+
return useQueries({
135+
queries: [
136+
{
137+
...options,
138+
queryKey: ['todos-key'],
139+
queryFn: () => Promise.resolve('data'),
140+
},
141+
],
142+
})
143+
}
144+
145+
const { value: queriesState } = useCustomQueries()
146+
const data = queriesState[0].data
147+
148+
const result: Expect<Equal<Data | undefined, typeof data>> = true
149+
return result
150+
})
151+
})
152+
})
153+
})

0 commit comments

Comments
 (0)