@tanstack/react-query 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/query-core/build/esm/index.js +3110 -0
- package/build/cjs/query-core/build/esm/index.js.map +1 -0
- package/build/cjs/react-query/src/Hydrate.js +66 -0
- package/build/cjs/react-query/src/Hydrate.js.map +1 -0
- package/build/cjs/react-query/src/QueryClientProvider.js +96 -0
- package/build/cjs/react-query/src/QueryClientProvider.js.map +1 -0
- package/build/cjs/react-query/src/QueryErrorResetBoundary.js +67 -0
- package/build/cjs/react-query/src/QueryErrorResetBoundary.js.map +1 -0
- package/build/cjs/react-query/src/index.js +64 -0
- package/build/cjs/react-query/src/index.js.map +1 -0
- package/build/cjs/react-query/src/isRestoring.js +43 -0
- package/build/cjs/react-query/src/isRestoring.js.map +1 -0
- package/build/cjs/react-query/src/useBaseQuery.js +117 -0
- package/build/cjs/react-query/src/useBaseQuery.js.map +1 -0
- package/build/cjs/react-query/src/useInfiniteQuery.js +24 -0
- package/build/cjs/react-query/src/useInfiniteQuery.js.map +1 -0
- package/build/cjs/react-query/src/useIsFetching.js +50 -0
- package/build/cjs/react-query/src/useIsFetching.js.map +1 -0
- package/build/cjs/react-query/src/useIsMutating.js +50 -0
- package/build/cjs/react-query/src/useIsMutating.js.map +1 -0
- package/build/cjs/react-query/src/useMutation.js +68 -0
- package/build/cjs/react-query/src/useMutation.js.map +1 -0
- package/build/cjs/react-query/src/useQueries.js +71 -0
- package/build/cjs/react-query/src/useQueries.js.map +1 -0
- package/build/cjs/react-query/src/useQuery.js +24 -0
- package/build/cjs/react-query/src/useQuery.js.map +1 -0
- package/build/cjs/react-query/src/utils.js +25 -0
- package/build/cjs/react-query/src/utils.js.map +1 -0
- package/build/esm/index.js +3368 -0
- package/build/esm/index.js.map +1 -0
- package/build/stats-html.html +2689 -0
- package/build/stats.json +666 -0
- package/build/types/packages/query-core/src/focusManager.d.ts +16 -0
- package/build/types/packages/query-core/src/hydration.d.ts +34 -0
- package/build/types/packages/query-core/src/index.d.ts +20 -0
- package/build/types/packages/query-core/src/infiniteQueryBehavior.d.ts +15 -0
- package/build/types/packages/query-core/src/infiniteQueryObserver.d.ts +18 -0
- package/build/types/packages/query-core/src/logger.d.ts +8 -0
- package/build/types/packages/query-core/src/mutation.d.ts +70 -0
- package/build/types/packages/query-core/src/mutationCache.d.ts +52 -0
- package/build/types/packages/query-core/src/mutationObserver.d.ts +23 -0
- package/build/types/packages/query-core/src/notifyManager.d.ts +18 -0
- package/build/types/packages/query-core/src/onlineManager.d.ts +16 -0
- package/build/types/packages/query-core/src/queriesObserver.d.ts +23 -0
- package/build/types/packages/query-core/src/query.d.ts +119 -0
- package/build/types/packages/query-core/src/queryCache.d.ts +59 -0
- package/build/types/packages/query-core/src/queryClient.d.ts +65 -0
- package/build/types/packages/query-core/src/queryObserver.d.ts +61 -0
- package/build/types/packages/query-core/src/removable.d.ts +9 -0
- package/build/types/packages/query-core/src/retryer.d.ts +33 -0
- package/build/types/packages/query-core/src/subscribable.d.ts +10 -0
- package/build/types/packages/query-core/src/types.d.ts +417 -0
- package/build/types/packages/query-core/src/utils.d.ts +99 -0
- package/build/types/packages/react-query/src/Hydrate.d.ts +10 -0
- package/build/types/packages/react-query/src/QueryClientProvider.d.ts +24 -0
- package/build/types/packages/react-query/src/QueryErrorResetBoundary.d.ts +12 -0
- package/build/types/packages/react-query/src/__tests__/Hydrate.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/QueryClientProvider.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.d.ts +6 -0
- package/build/types/packages/react-query/src/__tests__/ssr-hydration.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/ssr.test.d.ts +4 -0
- package/build/types/packages/react-query/src/__tests__/suspense.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useInfiniteQuery.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useIsFetching.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useIsMutating.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useMutation.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQueries.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQuery.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQuery.types.test.d.ts +2 -0
- package/build/types/packages/react-query/src/__tests__/utils.d.ts +8 -0
- package/build/types/packages/react-query/src/index.d.ts +17 -0
- package/build/types/packages/react-query/src/isRestoring.d.ts +3 -0
- package/build/types/packages/react-query/src/reactBatchedUpdates.d.ts +2 -0
- package/build/types/packages/react-query/src/reactBatchedUpdates.native.d.ts +2 -0
- package/build/types/packages/react-query/src/setBatchUpdatesFn.d.ts +1 -0
- package/build/types/packages/react-query/src/types.d.ts +35 -0
- package/build/types/packages/react-query/src/useBaseQuery.d.ts +3 -0
- package/build/types/packages/react-query/src/useInfiniteQuery.d.ts +5 -0
- package/build/types/packages/react-query/src/useIsFetching.d.ts +7 -0
- package/build/types/packages/react-query/src/useIsMutating.d.ts +7 -0
- package/build/types/packages/react-query/src/useMutation.d.ts +6 -0
- package/build/types/packages/react-query/src/useQueries.d.ts +49 -0
- package/build/types/packages/react-query/src/useQuery.d.ts +20 -0
- package/build/types/packages/react-query/src/utils.d.ts +1 -0
- package/build/types/tests/utils.d.ts +24 -0
- package/build/umd/index.development.js +3429 -0
- package/build/umd/index.development.js.map +1 -0
- package/build/umd/index.production.js +22 -0
- package/build/umd/index.production.js.map +1 -0
- package/codemods/v4/key-transformation.js +138 -0
- package/codemods/v4/replace-import-specifier.js +25 -0
- package/codemods/v4/utils/index.js +166 -0
- package/codemods/v4/utils/replacers/key-replacer.js +160 -0
- package/codemods/v4/utils/transformers/query-cache-transformer.js +115 -0
- package/codemods/v4/utils/transformers/query-client-transformer.js +49 -0
- package/codemods/v4/utils/transformers/use-query-like-transformer.js +32 -0
- package/codemods/v4/utils/unprocessable-key-error.js +8 -0
- package/package.json +63 -0
- package/src/Hydrate.tsx +36 -0
- package/src/QueryClientProvider.tsx +90 -0
- package/src/QueryErrorResetBoundary.tsx +52 -0
- package/src/__tests__/Hydrate.test.tsx +247 -0
- package/src/__tests__/QueryClientProvider.test.tsx +275 -0
- package/src/__tests__/QueryResetErrorBoundary.test.tsx +630 -0
- package/src/__tests__/ssr-hydration.test.tsx +274 -0
- package/src/__tests__/ssr.test.tsx +151 -0
- package/src/__tests__/suspense.test.tsx +1015 -0
- package/src/__tests__/useInfiniteQuery.test.tsx +1773 -0
- package/src/__tests__/useIsFetching.test.tsx +274 -0
- package/src/__tests__/useIsMutating.test.tsx +260 -0
- package/src/__tests__/useMutation.test.tsx +1099 -0
- package/src/__tests__/useQueries.test.tsx +1107 -0
- package/src/__tests__/useQuery.test.tsx +5746 -0
- package/src/__tests__/useQuery.types.test.tsx +157 -0
- package/src/__tests__/utils.tsx +45 -0
- package/src/index.ts +29 -0
- package/src/isRestoring.tsx +6 -0
- package/src/reactBatchedUpdates.native.ts +4 -0
- package/src/reactBatchedUpdates.ts +2 -0
- package/src/setBatchUpdatesFn.ts +4 -0
- package/src/types.ts +122 -0
- package/src/useBaseQuery.ts +140 -0
- package/src/useInfiniteQuery.ts +101 -0
- package/src/useIsFetching.ts +39 -0
- package/src/useIsMutating.ts +43 -0
- package/src/useMutation.ts +126 -0
- package/src/useQueries.ts +192 -0
- package/src/useQuery.ts +104 -0
- package/src/utils.ts +11 -0
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
import { fireEvent, waitFor } from '@testing-library/react'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
4
|
+
|
|
5
|
+
import * as QueriesObserverModule from '../../../query-core/src/queriesObserver'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createQueryClient,
|
|
9
|
+
expectType,
|
|
10
|
+
expectTypeNotAny,
|
|
11
|
+
queryKey,
|
|
12
|
+
sleep,
|
|
13
|
+
} from '../../../../tests/utils'
|
|
14
|
+
import { renderWithClient } from './utils'
|
|
15
|
+
import {
|
|
16
|
+
QueryClient,
|
|
17
|
+
useQueries,
|
|
18
|
+
UseQueryResult,
|
|
19
|
+
QueryCache,
|
|
20
|
+
QueryObserverResult,
|
|
21
|
+
QueriesObserver,
|
|
22
|
+
QueryFunction,
|
|
23
|
+
UseQueryOptions,
|
|
24
|
+
QueryKey,
|
|
25
|
+
} from '..'
|
|
26
|
+
import { QueryFunctionContext } from '@tanstack/query-core'
|
|
27
|
+
|
|
28
|
+
describe('useQueries', () => {
|
|
29
|
+
const queryCache = new QueryCache()
|
|
30
|
+
const queryClient = createQueryClient({ queryCache })
|
|
31
|
+
|
|
32
|
+
it('should return the correct states', async () => {
|
|
33
|
+
const key1 = queryKey()
|
|
34
|
+
const key2 = queryKey()
|
|
35
|
+
const results: UseQueryResult[][] = []
|
|
36
|
+
|
|
37
|
+
function Page() {
|
|
38
|
+
const result = useQueries({
|
|
39
|
+
queries: [
|
|
40
|
+
{
|
|
41
|
+
queryKey: key1,
|
|
42
|
+
queryFn: async () => {
|
|
43
|
+
await sleep(10)
|
|
44
|
+
return 1
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
queryKey: key2,
|
|
49
|
+
queryFn: async () => {
|
|
50
|
+
await sleep(100)
|
|
51
|
+
return 2
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
results.push(result)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div>
|
|
60
|
+
<div>
|
|
61
|
+
data1: {String(result[0].data ?? 'null')}, data2:{' '}
|
|
62
|
+
{String(result[1].data ?? 'null')}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
69
|
+
|
|
70
|
+
await waitFor(() => rendered.getByText('data1: 1, data2: 2'))
|
|
71
|
+
|
|
72
|
+
expect(results.length).toBe(3)
|
|
73
|
+
expect(results[0]).toMatchObject([{ data: undefined }, { data: undefined }])
|
|
74
|
+
expect(results[1]).toMatchObject([{ data: 1 }, { data: undefined }])
|
|
75
|
+
expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should keep previous data if amount of queries is the same', async () => {
|
|
79
|
+
const key1 = queryKey()
|
|
80
|
+
const key2 = queryKey()
|
|
81
|
+
const states: UseQueryResult[][] = []
|
|
82
|
+
|
|
83
|
+
function Page() {
|
|
84
|
+
const [count, setCount] = React.useState(1)
|
|
85
|
+
const result = useQueries({
|
|
86
|
+
queries: [
|
|
87
|
+
{
|
|
88
|
+
queryKey: [key1, count],
|
|
89
|
+
keepPreviousData: true,
|
|
90
|
+
queryFn: async () => {
|
|
91
|
+
await sleep(10)
|
|
92
|
+
return count * 2
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
queryKey: [key2, count],
|
|
97
|
+
keepPreviousData: true,
|
|
98
|
+
queryFn: async () => {
|
|
99
|
+
await sleep(35)
|
|
100
|
+
return count * 5
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
})
|
|
105
|
+
states.push(result)
|
|
106
|
+
|
|
107
|
+
const isFetching = result.some((r) => r.isFetching)
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div>
|
|
111
|
+
<div>
|
|
112
|
+
data1: {String(result[0].data ?? 'null')}, data2:{' '}
|
|
113
|
+
{String(result[1].data ?? 'null')}
|
|
114
|
+
</div>
|
|
115
|
+
<div>isFetching: {String(isFetching)}</div>
|
|
116
|
+
<button onClick={() => setCount((prev) => prev + 1)}>inc</button>
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
122
|
+
|
|
123
|
+
await waitFor(() => rendered.getByText('data1: 2, data2: 5'))
|
|
124
|
+
fireEvent.click(rendered.getByRole('button', { name: /inc/i }))
|
|
125
|
+
|
|
126
|
+
await waitFor(() => rendered.getByText('data1: 4, data2: 10'))
|
|
127
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
128
|
+
|
|
129
|
+
expect(states[states.length - 1]).toMatchObject([
|
|
130
|
+
{ status: 'success', data: 4, isPreviousData: false, isFetching: false },
|
|
131
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
132
|
+
])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should keep previous data for variable amounts of useQueries', async () => {
|
|
136
|
+
const key = queryKey()
|
|
137
|
+
const states: UseQueryResult[][] = []
|
|
138
|
+
|
|
139
|
+
function Page() {
|
|
140
|
+
const [count, setCount] = React.useState(2)
|
|
141
|
+
const result = useQueries({
|
|
142
|
+
queries: Array.from({ length: count }, (_, i) => ({
|
|
143
|
+
queryKey: [key, count, i + 1],
|
|
144
|
+
keepPreviousData: true,
|
|
145
|
+
queryFn: async () => {
|
|
146
|
+
await sleep(35 * (i + 1))
|
|
147
|
+
return (i + 1) * count * 2
|
|
148
|
+
},
|
|
149
|
+
})),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
states.push(result)
|
|
153
|
+
|
|
154
|
+
const isFetching = result.some((r) => r.isFetching)
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<div>data: {result.map((it) => it.data).join(',')}</div>
|
|
159
|
+
<div>isFetching: {String(isFetching)}</div>
|
|
160
|
+
<button onClick={() => setCount((prev) => prev + 1)}>inc</button>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
166
|
+
|
|
167
|
+
await waitFor(() => rendered.getByText('data: 4,8'))
|
|
168
|
+
fireEvent.click(rendered.getByRole('button', { name: /inc/i }))
|
|
169
|
+
|
|
170
|
+
await waitFor(() => rendered.getByText('data: 6,12,18'))
|
|
171
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
172
|
+
|
|
173
|
+
expect(states[states.length - 1]).toMatchObject([
|
|
174
|
+
{ status: 'success', data: 6, isPreviousData: false, isFetching: false },
|
|
175
|
+
{ status: 'success', data: 12, isPreviousData: false, isFetching: false },
|
|
176
|
+
{ status: 'success', data: 18, isPreviousData: false, isFetching: false },
|
|
177
|
+
])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should keep previous data when switching between queries', async () => {
|
|
181
|
+
const key = queryKey()
|
|
182
|
+
const states: UseQueryResult[][] = []
|
|
183
|
+
|
|
184
|
+
function Page() {
|
|
185
|
+
const [series1, setSeries1] = React.useState(1)
|
|
186
|
+
const [series2, setSeries2] = React.useState(2)
|
|
187
|
+
const ids = [series1, series2]
|
|
188
|
+
|
|
189
|
+
const result = useQueries({
|
|
190
|
+
queries: ids.map((id) => {
|
|
191
|
+
return {
|
|
192
|
+
queryKey: [key, id],
|
|
193
|
+
queryFn: async () => {
|
|
194
|
+
await sleep(5)
|
|
195
|
+
return id * 5
|
|
196
|
+
},
|
|
197
|
+
keepPreviousData: true,
|
|
198
|
+
}
|
|
199
|
+
}),
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
states.push(result)
|
|
203
|
+
|
|
204
|
+
const isFetching = result.some((r) => r.isFetching)
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<div>
|
|
208
|
+
<div>
|
|
209
|
+
data1: {String(result[0]?.data ?? 'null')}, data2:{' '}
|
|
210
|
+
{String(result[1]?.data ?? 'null')}
|
|
211
|
+
</div>
|
|
212
|
+
<div>isFetching: {String(isFetching)}</div>
|
|
213
|
+
<button onClick={() => setSeries2(3)}>setSeries2</button>
|
|
214
|
+
<button onClick={() => setSeries1(2)}>setSeries1</button>
|
|
215
|
+
</div>
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
220
|
+
|
|
221
|
+
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
|
|
222
|
+
fireEvent.click(rendered.getByRole('button', { name: /setSeries2/i }))
|
|
223
|
+
|
|
224
|
+
await waitFor(() => rendered.getByText('data1: 5, data2: 15'))
|
|
225
|
+
fireEvent.click(rendered.getByRole('button', { name: /setSeries1/i }))
|
|
226
|
+
|
|
227
|
+
await waitFor(() => rendered.getByText('data1: 10, data2: 15'))
|
|
228
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
229
|
+
|
|
230
|
+
expect(states[states.length - 1]).toMatchObject([
|
|
231
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
232
|
+
{ status: 'success', data: 15, isPreviousData: false, isFetching: false },
|
|
233
|
+
])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should not go to infinite render loop with previous data when toggling queries', async () => {
|
|
237
|
+
const key = queryKey()
|
|
238
|
+
const states: UseQueryResult[][] = []
|
|
239
|
+
|
|
240
|
+
function Page() {
|
|
241
|
+
const [enableId1, setEnableId1] = React.useState(true)
|
|
242
|
+
const ids = enableId1 ? [1, 2] : [2]
|
|
243
|
+
|
|
244
|
+
const result = useQueries({
|
|
245
|
+
queries: ids.map((id) => {
|
|
246
|
+
return {
|
|
247
|
+
queryKey: [key, id],
|
|
248
|
+
queryFn: async () => {
|
|
249
|
+
await sleep(5)
|
|
250
|
+
return id * 5
|
|
251
|
+
},
|
|
252
|
+
keepPreviousData: true,
|
|
253
|
+
}
|
|
254
|
+
}),
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
states.push(result)
|
|
258
|
+
|
|
259
|
+
const isFetching = result.some((r) => r.isFetching)
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<div>
|
|
264
|
+
data1: {String(result[0]?.data ?? 'null')}, data2:{' '}
|
|
265
|
+
{String(result[1]?.data ?? 'null')}
|
|
266
|
+
</div>
|
|
267
|
+
<div>isFetching: {String(isFetching)}</div>
|
|
268
|
+
<button onClick={() => setEnableId1(false)}>set1Disabled</button>
|
|
269
|
+
<button onClick={() => setEnableId1(true)}>set2Enabled</button>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
275
|
+
|
|
276
|
+
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
|
|
277
|
+
fireEvent.click(rendered.getByRole('button', { name: /set1Disabled/i }))
|
|
278
|
+
|
|
279
|
+
await waitFor(() => rendered.getByText('data1: 10, data2: null'))
|
|
280
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
281
|
+
fireEvent.click(rendered.getByRole('button', { name: /set2Enabled/i }))
|
|
282
|
+
|
|
283
|
+
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
|
|
284
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
285
|
+
|
|
286
|
+
await waitFor(() => expect(states.length).toBe(6))
|
|
287
|
+
|
|
288
|
+
expect(states[0]).toMatchObject([
|
|
289
|
+
{
|
|
290
|
+
status: 'loading',
|
|
291
|
+
data: undefined,
|
|
292
|
+
isPreviousData: false,
|
|
293
|
+
isFetching: true,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
status: 'loading',
|
|
297
|
+
data: undefined,
|
|
298
|
+
isPreviousData: false,
|
|
299
|
+
isFetching: true,
|
|
300
|
+
},
|
|
301
|
+
])
|
|
302
|
+
expect(states[1]).toMatchObject([
|
|
303
|
+
{ status: 'success', data: 5, isPreviousData: false, isFetching: false },
|
|
304
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
305
|
+
])
|
|
306
|
+
expect(states[2]).toMatchObject([
|
|
307
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
308
|
+
])
|
|
309
|
+
expect(states[3]).toMatchObject([
|
|
310
|
+
{ status: 'success', data: 5, isPreviousData: false, isFetching: true },
|
|
311
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
312
|
+
])
|
|
313
|
+
expect(states[4]).toMatchObject([
|
|
314
|
+
{ status: 'success', data: 5, isPreviousData: false, isFetching: true },
|
|
315
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
316
|
+
])
|
|
317
|
+
expect(states[5]).toMatchObject([
|
|
318
|
+
{ status: 'success', data: 5, isPreviousData: false, isFetching: false },
|
|
319
|
+
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
|
|
320
|
+
])
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('handles type parameter - tuple of tuples', async () => {
|
|
324
|
+
const key1 = queryKey()
|
|
325
|
+
const key2 = queryKey()
|
|
326
|
+
const key3 = queryKey()
|
|
327
|
+
|
|
328
|
+
// @ts-expect-error (Page component is not rendered)
|
|
329
|
+
// eslint-disable-next-line
|
|
330
|
+
function Page() {
|
|
331
|
+
const result1 = useQueries<[[number], [string], [string[], boolean]]>({
|
|
332
|
+
queries: [
|
|
333
|
+
{
|
|
334
|
+
queryKey: key1,
|
|
335
|
+
queryFn: () => 1,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
queryKey: key2,
|
|
339
|
+
queryFn: () => 'string',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
queryKey: key3,
|
|
343
|
+
queryFn: () => ['string[]'],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
})
|
|
347
|
+
expectType<QueryObserverResult<number, unknown>>(result1[0])
|
|
348
|
+
expectType<QueryObserverResult<string, unknown>>(result1[1])
|
|
349
|
+
expectType<QueryObserverResult<string[], boolean>>(result1[2])
|
|
350
|
+
expectType<number | undefined>(result1[0].data)
|
|
351
|
+
expectType<string | undefined>(result1[1].data)
|
|
352
|
+
expectType<string[] | undefined>(result1[2].data)
|
|
353
|
+
expectType<boolean | null>(result1[2].error)
|
|
354
|
+
|
|
355
|
+
// TData (3rd element) takes precedence over TQueryFnData (1st element)
|
|
356
|
+
const result2 = useQueries<
|
|
357
|
+
[[string, unknown, string], [string, unknown, number]]
|
|
358
|
+
>({
|
|
359
|
+
queries: [
|
|
360
|
+
{
|
|
361
|
+
queryKey: key1,
|
|
362
|
+
queryFn: () => 'string',
|
|
363
|
+
select: (a) => {
|
|
364
|
+
expectType<string>(a)
|
|
365
|
+
expectTypeNotAny(a)
|
|
366
|
+
return a.toLowerCase()
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
queryKey: key2,
|
|
371
|
+
queryFn: () => 'string',
|
|
372
|
+
select: (a) => {
|
|
373
|
+
expectType<string>(a)
|
|
374
|
+
expectTypeNotAny(a)
|
|
375
|
+
return parseInt(a)
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
})
|
|
380
|
+
expectType<QueryObserverResult<string, unknown>>(result2[0])
|
|
381
|
+
expectType<QueryObserverResult<number, unknown>>(result2[1])
|
|
382
|
+
expectType<string | undefined>(result2[0].data)
|
|
383
|
+
expectType<number | undefined>(result2[1].data)
|
|
384
|
+
|
|
385
|
+
// types should be enforced
|
|
386
|
+
useQueries<[[string, unknown, string], [string, boolean, number]]>({
|
|
387
|
+
queries: [
|
|
388
|
+
{
|
|
389
|
+
queryKey: key1,
|
|
390
|
+
queryFn: () => 'string',
|
|
391
|
+
select: (a) => {
|
|
392
|
+
expectType<string>(a)
|
|
393
|
+
expectTypeNotAny(a)
|
|
394
|
+
return a.toLowerCase()
|
|
395
|
+
},
|
|
396
|
+
onSuccess: (a) => {
|
|
397
|
+
expectType<string>(a)
|
|
398
|
+
expectTypeNotAny(a)
|
|
399
|
+
},
|
|
400
|
+
placeholderData: 'string',
|
|
401
|
+
// @ts-expect-error (initialData: string)
|
|
402
|
+
initialData: 123,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
queryKey: key2,
|
|
406
|
+
queryFn: () => 'string',
|
|
407
|
+
select: (a) => {
|
|
408
|
+
expectType<string>(a)
|
|
409
|
+
expectTypeNotAny(a)
|
|
410
|
+
return parseInt(a)
|
|
411
|
+
},
|
|
412
|
+
onSuccess: (a) => {
|
|
413
|
+
expectType<number>(a)
|
|
414
|
+
expectTypeNotAny(a)
|
|
415
|
+
},
|
|
416
|
+
onError: (e) => {
|
|
417
|
+
expectType<boolean>(e)
|
|
418
|
+
expectTypeNotAny(e)
|
|
419
|
+
},
|
|
420
|
+
placeholderData: 'string',
|
|
421
|
+
// @ts-expect-error (initialData: string)
|
|
422
|
+
initialData: 123,
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
// field names should be enforced
|
|
428
|
+
useQueries<[[string]]>({
|
|
429
|
+
queries: [
|
|
430
|
+
{
|
|
431
|
+
queryKey: key1,
|
|
432
|
+
queryFn: () => 'string',
|
|
433
|
+
// @ts-expect-error (invalidField)
|
|
434
|
+
someInvalidField: [],
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('handles type parameter - tuple of objects', async () => {
|
|
442
|
+
const key1 = queryKey()
|
|
443
|
+
const key2 = queryKey()
|
|
444
|
+
const key3 = queryKey()
|
|
445
|
+
|
|
446
|
+
// @ts-expect-error (Page component is not rendered)
|
|
447
|
+
// eslint-disable-next-line
|
|
448
|
+
function Page() {
|
|
449
|
+
const result1 = useQueries<
|
|
450
|
+
[
|
|
451
|
+
{ queryFnData: number },
|
|
452
|
+
{ queryFnData: string },
|
|
453
|
+
{ queryFnData: string[]; error: boolean },
|
|
454
|
+
]
|
|
455
|
+
>({
|
|
456
|
+
queries: [
|
|
457
|
+
{
|
|
458
|
+
queryKey: key1,
|
|
459
|
+
queryFn: () => 1,
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
queryKey: key2,
|
|
463
|
+
queryFn: () => 'string',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
queryKey: key3,
|
|
467
|
+
queryFn: () => ['string[]'],
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
})
|
|
471
|
+
expectType<QueryObserverResult<number, unknown>>(result1[0])
|
|
472
|
+
expectType<QueryObserverResult<string, unknown>>(result1[1])
|
|
473
|
+
expectType<QueryObserverResult<string[], boolean>>(result1[2])
|
|
474
|
+
expectType<number | undefined>(result1[0].data)
|
|
475
|
+
expectType<string | undefined>(result1[1].data)
|
|
476
|
+
expectType<string[] | undefined>(result1[2].data)
|
|
477
|
+
expectType<boolean | null>(result1[2].error)
|
|
478
|
+
|
|
479
|
+
// TData (data prop) takes precedence over TQueryFnData (queryFnData prop)
|
|
480
|
+
const result2 = useQueries<
|
|
481
|
+
[
|
|
482
|
+
{ queryFnData: string; data: string },
|
|
483
|
+
{ queryFnData: string; data: number },
|
|
484
|
+
]
|
|
485
|
+
>({
|
|
486
|
+
queries: [
|
|
487
|
+
{
|
|
488
|
+
queryKey: key1,
|
|
489
|
+
queryFn: () => 'string',
|
|
490
|
+
select: (a) => {
|
|
491
|
+
expectType<string>(a)
|
|
492
|
+
expectTypeNotAny(a)
|
|
493
|
+
return a.toLowerCase()
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
queryKey: key2,
|
|
498
|
+
queryFn: () => 'string',
|
|
499
|
+
select: (a) => {
|
|
500
|
+
expectType<string>(a)
|
|
501
|
+
expectTypeNotAny(a)
|
|
502
|
+
return parseInt(a)
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
})
|
|
507
|
+
expectType<QueryObserverResult<string, unknown>>(result2[0])
|
|
508
|
+
expectType<QueryObserverResult<number, unknown>>(result2[1])
|
|
509
|
+
expectType<string | undefined>(result2[0].data)
|
|
510
|
+
expectType<number | undefined>(result2[1].data)
|
|
511
|
+
|
|
512
|
+
// can pass only TData (data prop) although TQueryFnData will be left unknown
|
|
513
|
+
const result3 = useQueries<[{ data: string }, { data: number }]>({
|
|
514
|
+
queries: [
|
|
515
|
+
{
|
|
516
|
+
queryKey: key1,
|
|
517
|
+
queryFn: () => 'string',
|
|
518
|
+
select: (a) => {
|
|
519
|
+
expectType<unknown>(a)
|
|
520
|
+
expectTypeNotAny(a)
|
|
521
|
+
return a as string
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
queryKey: key2,
|
|
526
|
+
queryFn: () => 'string',
|
|
527
|
+
select: (a) => {
|
|
528
|
+
expectType<unknown>(a)
|
|
529
|
+
expectTypeNotAny(a)
|
|
530
|
+
return a as number
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
})
|
|
535
|
+
expectType<QueryObserverResult<string, unknown>>(result3[0])
|
|
536
|
+
expectType<QueryObserverResult<number, unknown>>(result3[1])
|
|
537
|
+
expectType<string | undefined>(result3[0].data)
|
|
538
|
+
expectType<number | undefined>(result3[1].data)
|
|
539
|
+
|
|
540
|
+
// types should be enforced
|
|
541
|
+
useQueries<
|
|
542
|
+
[
|
|
543
|
+
{ queryFnData: string; data: string },
|
|
544
|
+
{ queryFnData: string; data: number; error: boolean },
|
|
545
|
+
]
|
|
546
|
+
>({
|
|
547
|
+
queries: [
|
|
548
|
+
{
|
|
549
|
+
queryKey: key1,
|
|
550
|
+
queryFn: () => 'string',
|
|
551
|
+
select: (a) => {
|
|
552
|
+
expectType<string>(a)
|
|
553
|
+
expectTypeNotAny(a)
|
|
554
|
+
return a.toLowerCase()
|
|
555
|
+
},
|
|
556
|
+
onSuccess: (a) => {
|
|
557
|
+
expectType<string>(a)
|
|
558
|
+
expectTypeNotAny(a)
|
|
559
|
+
},
|
|
560
|
+
placeholderData: 'string',
|
|
561
|
+
// @ts-expect-error (initialData: string)
|
|
562
|
+
initialData: 123,
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
queryKey: key2,
|
|
566
|
+
queryFn: () => 'string',
|
|
567
|
+
select: (a) => {
|
|
568
|
+
expectType<string>(a)
|
|
569
|
+
expectTypeNotAny(a)
|
|
570
|
+
return parseInt(a)
|
|
571
|
+
},
|
|
572
|
+
onSuccess: (a) => {
|
|
573
|
+
expectType<number>(a)
|
|
574
|
+
expectTypeNotAny(a)
|
|
575
|
+
},
|
|
576
|
+
onError: (e) => {
|
|
577
|
+
expectType<boolean>(e)
|
|
578
|
+
expectTypeNotAny(e)
|
|
579
|
+
},
|
|
580
|
+
placeholderData: 'string',
|
|
581
|
+
// @ts-expect-error (initialData: string)
|
|
582
|
+
initialData: 123,
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// field names should be enforced
|
|
588
|
+
useQueries<[{ queryFnData: string }]>({
|
|
589
|
+
queries: [
|
|
590
|
+
{
|
|
591
|
+
queryKey: key1,
|
|
592
|
+
queryFn: () => 'string',
|
|
593
|
+
// @ts-expect-error (invalidField)
|
|
594
|
+
someInvalidField: [],
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('handles array literal without type parameter to infer result type', async () => {
|
|
602
|
+
const key1 = queryKey()
|
|
603
|
+
const key2 = queryKey()
|
|
604
|
+
const key3 = queryKey()
|
|
605
|
+
const key4 = queryKey()
|
|
606
|
+
|
|
607
|
+
// @ts-expect-error (Page component is not rendered)
|
|
608
|
+
// eslint-disable-next-line
|
|
609
|
+
function Page() {
|
|
610
|
+
// Array.map preserves TQueryFnData
|
|
611
|
+
const result1 = useQueries({
|
|
612
|
+
queries: Array(50).map((_, i) => ({
|
|
613
|
+
queryKey: ['key', i] as const,
|
|
614
|
+
queryFn: () => i + 10,
|
|
615
|
+
})),
|
|
616
|
+
})
|
|
617
|
+
expectType<QueryObserverResult<number, unknown>[]>(result1)
|
|
618
|
+
expectType<number | undefined>(result1[0]?.data)
|
|
619
|
+
|
|
620
|
+
// Array.map preserves TData
|
|
621
|
+
const result2 = useQueries({
|
|
622
|
+
queries: Array(50).map((_, i) => ({
|
|
623
|
+
queryKey: ['key', i] as const,
|
|
624
|
+
queryFn: () => i + 10,
|
|
625
|
+
select: (data: number) => data.toString(),
|
|
626
|
+
})),
|
|
627
|
+
})
|
|
628
|
+
expectType<QueryObserverResult<string, unknown>[]>(result2)
|
|
629
|
+
|
|
630
|
+
const result3 = useQueries({
|
|
631
|
+
queries: [
|
|
632
|
+
{
|
|
633
|
+
queryKey: key1,
|
|
634
|
+
queryFn: () => 1,
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
queryKey: key2,
|
|
638
|
+
queryFn: () => 'string',
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
queryKey: key3,
|
|
642
|
+
queryFn: () => ['string[]'],
|
|
643
|
+
select: () => 123,
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
})
|
|
647
|
+
expectType<QueryObserverResult<number, unknown>>(result3[0])
|
|
648
|
+
expectType<QueryObserverResult<string, unknown>>(result3[1])
|
|
649
|
+
expectType<QueryObserverResult<number, unknown>>(result3[2])
|
|
650
|
+
expectType<number | undefined>(result3[0].data)
|
|
651
|
+
expectType<string | undefined>(result3[1].data)
|
|
652
|
+
// select takes precedence over queryFn
|
|
653
|
+
expectType<number | undefined>(result3[2].data)
|
|
654
|
+
|
|
655
|
+
// initialData/placeholderData are enforced
|
|
656
|
+
useQueries({
|
|
657
|
+
queries: [
|
|
658
|
+
{
|
|
659
|
+
queryKey: key1,
|
|
660
|
+
queryFn: () => 'string',
|
|
661
|
+
placeholderData: 'string',
|
|
662
|
+
// @ts-expect-error (initialData: string)
|
|
663
|
+
initialData: 123,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
queryKey: key2,
|
|
667
|
+
queryFn: () => 123,
|
|
668
|
+
// @ts-expect-error (placeholderData: number)
|
|
669
|
+
placeholderData: 'string',
|
|
670
|
+
initialData: 123,
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
// select / onSuccess / onSettled params are "indirectly" enforced
|
|
676
|
+
useQueries({
|
|
677
|
+
queries: [
|
|
678
|
+
// unfortunately TS will not suggest the type for you
|
|
679
|
+
{
|
|
680
|
+
queryKey: key1,
|
|
681
|
+
queryFn: () => 'string',
|
|
682
|
+
// @ts-expect-error (noImplicitAny)
|
|
683
|
+
onSuccess: (a) => null,
|
|
684
|
+
// @ts-expect-error (noImplicitAny)
|
|
685
|
+
onSettled: (a) => null,
|
|
686
|
+
},
|
|
687
|
+
// however you can add a type to the callback
|
|
688
|
+
{
|
|
689
|
+
queryKey: key2,
|
|
690
|
+
queryFn: () => 'string',
|
|
691
|
+
onSuccess: (a: string) => {
|
|
692
|
+
expectType<string>(a)
|
|
693
|
+
expectTypeNotAny(a)
|
|
694
|
+
},
|
|
695
|
+
onSettled: (a: string | undefined) => {
|
|
696
|
+
expectType<string | undefined>(a)
|
|
697
|
+
expectTypeNotAny(a)
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
// the type you do pass is enforced
|
|
701
|
+
{
|
|
702
|
+
queryKey: key3,
|
|
703
|
+
queryFn: () => 'string',
|
|
704
|
+
// @ts-expect-error (only accepts string)
|
|
705
|
+
onSuccess: (a: number) => null,
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
queryKey: key4,
|
|
709
|
+
queryFn: () => 'string',
|
|
710
|
+
select: (a: string) => parseInt(a),
|
|
711
|
+
// @ts-expect-error (select is defined => only accepts number)
|
|
712
|
+
onSuccess: (a: string) => null,
|
|
713
|
+
onSettled: (a: number | undefined) => {
|
|
714
|
+
expectType<number | undefined>(a)
|
|
715
|
+
expectTypeNotAny(a)
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
// callbacks are also indirectly enforced with Array.map
|
|
722
|
+
useQueries({
|
|
723
|
+
// @ts-expect-error (onSuccess only accepts string)
|
|
724
|
+
queries: Array(50).map((_, i) => ({
|
|
725
|
+
queryKey: ['key', i] as const,
|
|
726
|
+
queryFn: () => i + 10,
|
|
727
|
+
select: (data: number) => data.toString(),
|
|
728
|
+
onSuccess: (_data: number) => null,
|
|
729
|
+
})),
|
|
730
|
+
})
|
|
731
|
+
useQueries({
|
|
732
|
+
queries: Array(50).map((_, i) => ({
|
|
733
|
+
queryKey: ['key', i] as const,
|
|
734
|
+
queryFn: () => i + 10,
|
|
735
|
+
select: (data: number) => data.toString(),
|
|
736
|
+
onSuccess: (_data: string) => null,
|
|
737
|
+
})),
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
// results inference works when all the handlers are defined
|
|
741
|
+
const result4 = useQueries({
|
|
742
|
+
queries: [
|
|
743
|
+
{
|
|
744
|
+
queryKey: key1,
|
|
745
|
+
queryFn: () => 'string',
|
|
746
|
+
// @ts-expect-error (noImplicitAny)
|
|
747
|
+
onSuccess: (a) => null,
|
|
748
|
+
// @ts-expect-error (noImplicitAny)
|
|
749
|
+
onSettled: (a) => null,
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
queryKey: key2,
|
|
753
|
+
queryFn: () => 'string',
|
|
754
|
+
onSuccess: (a: string) => {
|
|
755
|
+
expectType<string>(a)
|
|
756
|
+
expectTypeNotAny(a)
|
|
757
|
+
},
|
|
758
|
+
onSettled: (a: string | undefined) => {
|
|
759
|
+
expectType<string | undefined>(a)
|
|
760
|
+
expectTypeNotAny(a)
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
queryKey: key4,
|
|
765
|
+
queryFn: () => 'string',
|
|
766
|
+
select: (a: string) => parseInt(a),
|
|
767
|
+
onSuccess: (_a: number) => null,
|
|
768
|
+
onSettled: (a: number | undefined) => {
|
|
769
|
+
expectType<number | undefined>(a)
|
|
770
|
+
expectTypeNotAny(a)
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
})
|
|
775
|
+
expectType<QueryObserverResult<string, unknown>>(result4[0])
|
|
776
|
+
expectType<QueryObserverResult<string, unknown>>(result4[1])
|
|
777
|
+
expectType<QueryObserverResult<number, unknown>>(result4[2])
|
|
778
|
+
|
|
779
|
+
// handles when queryFn returns a Promise
|
|
780
|
+
const result5 = useQueries({
|
|
781
|
+
queries: [
|
|
782
|
+
{
|
|
783
|
+
queryKey: key1,
|
|
784
|
+
queryFn: () => Promise.resolve('string'),
|
|
785
|
+
onSuccess: (a: string) => {
|
|
786
|
+
expectType<string>(a)
|
|
787
|
+
expectTypeNotAny(a)
|
|
788
|
+
},
|
|
789
|
+
// @ts-expect-error (refuses to accept a Promise)
|
|
790
|
+
onSettled: (a: Promise<string>) => null,
|
|
791
|
+
},
|
|
792
|
+
],
|
|
793
|
+
})
|
|
794
|
+
expectType<QueryObserverResult<string, unknown>>(result5[0])
|
|
795
|
+
|
|
796
|
+
// Array as const does not throw error
|
|
797
|
+
const result6 = useQueries({
|
|
798
|
+
queries: [
|
|
799
|
+
{
|
|
800
|
+
queryKey: ['key1'],
|
|
801
|
+
queryFn: () => 'string',
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
queryKey: ['key1'],
|
|
805
|
+
queryFn: () => 123,
|
|
806
|
+
},
|
|
807
|
+
],
|
|
808
|
+
} as const)
|
|
809
|
+
expectType<QueryObserverResult<string, unknown>>(result6[0])
|
|
810
|
+
expectType<QueryObserverResult<number, unknown>>(result6[1])
|
|
811
|
+
|
|
812
|
+
// field names should be enforced - array literal
|
|
813
|
+
useQueries({
|
|
814
|
+
queries: [
|
|
815
|
+
{
|
|
816
|
+
queryKey: key1,
|
|
817
|
+
queryFn: () => 'string',
|
|
818
|
+
// @ts-expect-error (invalidField)
|
|
819
|
+
someInvalidField: [],
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
// field names should be enforced - Array.map() result
|
|
825
|
+
useQueries({
|
|
826
|
+
// @ts-expect-error (invalidField)
|
|
827
|
+
queries: Array(10).map(() => ({
|
|
828
|
+
someInvalidField: '',
|
|
829
|
+
})),
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
// field names should be enforced - array literal
|
|
833
|
+
useQueries({
|
|
834
|
+
queries: [
|
|
835
|
+
{
|
|
836
|
+
queryKey: key1,
|
|
837
|
+
queryFn: () => 'string',
|
|
838
|
+
// @ts-expect-error (invalidField)
|
|
839
|
+
someInvalidField: [],
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
// supports queryFn using fetch() to return Promise<any> - Array.map() result
|
|
845
|
+
useQueries({
|
|
846
|
+
queries: Array(50).map((_, i) => ({
|
|
847
|
+
queryKey: ['key', i] as const,
|
|
848
|
+
queryFn: () =>
|
|
849
|
+
fetch('return Promise<any>').then((resp) => resp.json()),
|
|
850
|
+
})),
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
// supports queryFn using fetch() to return Promise<any> - array literal
|
|
854
|
+
useQueries({
|
|
855
|
+
queries: [
|
|
856
|
+
{
|
|
857
|
+
queryKey: key1,
|
|
858
|
+
queryFn: () =>
|
|
859
|
+
fetch('return Promise<any>').then((resp) => resp.json()),
|
|
860
|
+
},
|
|
861
|
+
],
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
it('handles strongly typed queryFn factories and useQueries wrappers', () => {
|
|
867
|
+
// QueryKey + queryFn factory
|
|
868
|
+
type QueryKeyA = ['queryA']
|
|
869
|
+
const getQueryKeyA = (): QueryKeyA => ['queryA']
|
|
870
|
+
type GetQueryFunctionA = () => QueryFunction<number, QueryKeyA>
|
|
871
|
+
const getQueryFunctionA: GetQueryFunctionA = () => async () => {
|
|
872
|
+
return 1
|
|
873
|
+
}
|
|
874
|
+
type SelectorA = (data: number) => [number, string]
|
|
875
|
+
const getSelectorA = (): SelectorA => (data) => [data, data.toString()]
|
|
876
|
+
|
|
877
|
+
type QueryKeyB = ['queryB', string]
|
|
878
|
+
const getQueryKeyB = (id: string): QueryKeyB => ['queryB', id]
|
|
879
|
+
type GetQueryFunctionB = () => QueryFunction<string, QueryKeyB>
|
|
880
|
+
const getQueryFunctionB: GetQueryFunctionB = () => async () => {
|
|
881
|
+
return '1'
|
|
882
|
+
}
|
|
883
|
+
type SelectorB = (data: string) => [string, number]
|
|
884
|
+
const getSelectorB = (): SelectorB => (data) => [data, +data]
|
|
885
|
+
|
|
886
|
+
// Wrapper with strongly typed array-parameter
|
|
887
|
+
function useWrappedQueries<
|
|
888
|
+
TQueryFnData,
|
|
889
|
+
TError,
|
|
890
|
+
TData,
|
|
891
|
+
TQueryKey extends QueryKey,
|
|
892
|
+
>(queries: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>[]) {
|
|
893
|
+
return useQueries({
|
|
894
|
+
queries: queries.map(
|
|
895
|
+
// no need to type the mapped query
|
|
896
|
+
(query) => {
|
|
897
|
+
const { queryFn: fn, queryKey: key, onError: err } = query
|
|
898
|
+
expectType<QueryFunction<TQueryFnData, TQueryKey> | undefined>(fn)
|
|
899
|
+
return {
|
|
900
|
+
queryKey: key,
|
|
901
|
+
onError: err,
|
|
902
|
+
queryFn: fn
|
|
903
|
+
? (ctx: QueryFunctionContext<TQueryKey>) => {
|
|
904
|
+
expectType<TQueryKey>(ctx.queryKey)
|
|
905
|
+
return fn.call({}, ctx)
|
|
906
|
+
}
|
|
907
|
+
: undefined,
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
),
|
|
911
|
+
})
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// @ts-expect-error (Page component is not rendered)
|
|
915
|
+
// eslint-disable-next-line
|
|
916
|
+
function Page() {
|
|
917
|
+
const result = useQueries({
|
|
918
|
+
queries: [
|
|
919
|
+
{
|
|
920
|
+
queryKey: getQueryKeyA(),
|
|
921
|
+
queryFn: getQueryFunctionA(),
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
queryKey: getQueryKeyB('id'),
|
|
925
|
+
queryFn: getQueryFunctionB(),
|
|
926
|
+
},
|
|
927
|
+
],
|
|
928
|
+
})
|
|
929
|
+
expectType<QueryObserverResult<number, unknown>>(result[0])
|
|
930
|
+
expectType<QueryObserverResult<string, unknown>>(result[1])
|
|
931
|
+
|
|
932
|
+
const withSelector = useQueries({
|
|
933
|
+
queries: [
|
|
934
|
+
{
|
|
935
|
+
queryKey: getQueryKeyA(),
|
|
936
|
+
queryFn: getQueryFunctionA(),
|
|
937
|
+
select: getSelectorA(),
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
queryKey: getQueryKeyB('id'),
|
|
941
|
+
queryFn: getQueryFunctionB(),
|
|
942
|
+
select: getSelectorB(),
|
|
943
|
+
},
|
|
944
|
+
],
|
|
945
|
+
})
|
|
946
|
+
expectType<QueryObserverResult<[number, string], unknown>>(
|
|
947
|
+
withSelector[0],
|
|
948
|
+
)
|
|
949
|
+
expectType<QueryObserverResult<[string, number], unknown>>(
|
|
950
|
+
withSelector[1],
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
const withWrappedQueries = useWrappedQueries(
|
|
954
|
+
Array(10).map(() => ({
|
|
955
|
+
queryKey: getQueryKeyA(),
|
|
956
|
+
queryFn: getQueryFunctionA(),
|
|
957
|
+
select: getSelectorA(),
|
|
958
|
+
})),
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
expectType<QueryObserverResult<number | undefined, unknown>[]>(
|
|
962
|
+
withWrappedQueries,
|
|
963
|
+
)
|
|
964
|
+
}
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
it('should not change state if unmounted', async () => {
|
|
968
|
+
const key1 = queryKey()
|
|
969
|
+
|
|
970
|
+
// We have to mock the QueriesObserver to not unsubscribe
|
|
971
|
+
// the listener when the component is unmounted
|
|
972
|
+
class QueriesObserverMock extends QueriesObserver {
|
|
973
|
+
subscribe(listener: any) {
|
|
974
|
+
super.subscribe(listener)
|
|
975
|
+
return () => void 0
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const QueriesObserverSpy = jest
|
|
980
|
+
.spyOn(QueriesObserverModule, 'QueriesObserver')
|
|
981
|
+
.mockImplementation((fn) => {
|
|
982
|
+
return new QueriesObserverMock(fn)
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
function Queries() {
|
|
986
|
+
useQueries({
|
|
987
|
+
queries: [
|
|
988
|
+
{
|
|
989
|
+
queryKey: key1,
|
|
990
|
+
queryFn: async () => {
|
|
991
|
+
await sleep(10)
|
|
992
|
+
return 1
|
|
993
|
+
},
|
|
994
|
+
},
|
|
995
|
+
],
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
return (
|
|
999
|
+
<div>
|
|
1000
|
+
<span>queries</span>
|
|
1001
|
+
</div>
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function Page() {
|
|
1006
|
+
const [mounted, setMounted] = React.useState(true)
|
|
1007
|
+
|
|
1008
|
+
return (
|
|
1009
|
+
<div>
|
|
1010
|
+
<button onClick={() => setMounted(false)}>unmount</button>
|
|
1011
|
+
{mounted && <Queries />}
|
|
1012
|
+
</div>
|
|
1013
|
+
)
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const { getByText } = renderWithClient(queryClient, <Page />)
|
|
1017
|
+
fireEvent.click(getByText('unmount'))
|
|
1018
|
+
|
|
1019
|
+
// Should not display the console error
|
|
1020
|
+
// "Warning: Can't perform a React state update on an unmounted component"
|
|
1021
|
+
|
|
1022
|
+
await sleep(20)
|
|
1023
|
+
QueriesObserverSpy.mockRestore()
|
|
1024
|
+
})
|
|
1025
|
+
|
|
1026
|
+
describe('with custom context', () => {
|
|
1027
|
+
it('should return the correct states', async () => {
|
|
1028
|
+
const context = React.createContext<QueryClient | undefined>(undefined)
|
|
1029
|
+
|
|
1030
|
+
const key1 = queryKey()
|
|
1031
|
+
const key2 = queryKey()
|
|
1032
|
+
const results: UseQueryResult[][] = []
|
|
1033
|
+
|
|
1034
|
+
function Page() {
|
|
1035
|
+
const result = useQueries({
|
|
1036
|
+
context,
|
|
1037
|
+
queries: [
|
|
1038
|
+
{
|
|
1039
|
+
queryKey: key1,
|
|
1040
|
+
queryFn: async () => {
|
|
1041
|
+
await sleep(5)
|
|
1042
|
+
return 1
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
queryKey: key2,
|
|
1047
|
+
queryFn: async () => {
|
|
1048
|
+
await sleep(10)
|
|
1049
|
+
return 2
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
],
|
|
1053
|
+
})
|
|
1054
|
+
results.push(result)
|
|
1055
|
+
return null
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
renderWithClient(queryClient, <Page />, { context })
|
|
1059
|
+
|
|
1060
|
+
await sleep(30)
|
|
1061
|
+
|
|
1062
|
+
expect(results[0]).toMatchObject([
|
|
1063
|
+
{ data: undefined },
|
|
1064
|
+
{ data: undefined },
|
|
1065
|
+
])
|
|
1066
|
+
expect(results[results.length - 1]).toMatchObject([
|
|
1067
|
+
{ data: 1 },
|
|
1068
|
+
{ data: 2 },
|
|
1069
|
+
])
|
|
1070
|
+
})
|
|
1071
|
+
|
|
1072
|
+
it('should throw if the context is necessary and is not passed to useQueries', async () => {
|
|
1073
|
+
const context = React.createContext<QueryClient | undefined>(undefined)
|
|
1074
|
+
|
|
1075
|
+
const key1 = queryKey()
|
|
1076
|
+
const key2 = queryKey()
|
|
1077
|
+
const results: UseQueryResult[][] = []
|
|
1078
|
+
|
|
1079
|
+
function Page() {
|
|
1080
|
+
const result = useQueries({
|
|
1081
|
+
queries: [
|
|
1082
|
+
{
|
|
1083
|
+
queryKey: key1,
|
|
1084
|
+
queryFn: async () => 1,
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
queryKey: key2,
|
|
1088
|
+
queryFn: async () => 2,
|
|
1089
|
+
},
|
|
1090
|
+
],
|
|
1091
|
+
})
|
|
1092
|
+
results.push(result)
|
|
1093
|
+
return null
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const rendered = renderWithClient(
|
|
1097
|
+
queryClient,
|
|
1098
|
+
<ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
|
|
1099
|
+
<Page />
|
|
1100
|
+
</ErrorBoundary>,
|
|
1101
|
+
{ context },
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
await waitFor(() => rendered.getByText('error boundary'))
|
|
1105
|
+
})
|
|
1106
|
+
})
|
|
1107
|
+
})
|