@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,1773 @@
|
|
|
1
|
+
import { waitFor, fireEvent } from '@testing-library/react'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
queryKey,
|
|
6
|
+
sleep,
|
|
7
|
+
setActTimeout,
|
|
8
|
+
createQueryClient,
|
|
9
|
+
} from '../../../../tests/utils'
|
|
10
|
+
|
|
11
|
+
import { renderWithClient, Blink } from './utils'
|
|
12
|
+
import {
|
|
13
|
+
useInfiniteQuery,
|
|
14
|
+
UseInfiniteQueryResult,
|
|
15
|
+
QueryCache,
|
|
16
|
+
QueryFunctionContext,
|
|
17
|
+
InfiniteData,
|
|
18
|
+
} from '..'
|
|
19
|
+
|
|
20
|
+
interface Result {
|
|
21
|
+
items: number[]
|
|
22
|
+
nextId?: number
|
|
23
|
+
prevId?: number
|
|
24
|
+
ts: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pageSize = 10
|
|
28
|
+
|
|
29
|
+
const fetchItems = async (
|
|
30
|
+
page: number,
|
|
31
|
+
ts: number,
|
|
32
|
+
noNext?: boolean,
|
|
33
|
+
noPrev?: boolean,
|
|
34
|
+
): Promise<Result> => {
|
|
35
|
+
await sleep(10)
|
|
36
|
+
return {
|
|
37
|
+
items: [...new Array(10)].fill(null).map((_, d) => page * pageSize + d),
|
|
38
|
+
nextId: noNext ? undefined : page + 1,
|
|
39
|
+
prevId: noPrev ? undefined : page - 1,
|
|
40
|
+
ts,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('useInfiniteQuery', () => {
|
|
45
|
+
const queryCache = new QueryCache()
|
|
46
|
+
const queryClient = createQueryClient({ queryCache })
|
|
47
|
+
|
|
48
|
+
it('should return the correct states for a successful query', async () => {
|
|
49
|
+
const key = queryKey()
|
|
50
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
51
|
+
|
|
52
|
+
function Page() {
|
|
53
|
+
const state = useInfiniteQuery(
|
|
54
|
+
key,
|
|
55
|
+
({ pageParam = 0 }) => Number(pageParam),
|
|
56
|
+
{
|
|
57
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
states.push(state)
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
renderWithClient(queryClient, <Page />)
|
|
65
|
+
|
|
66
|
+
await sleep(100)
|
|
67
|
+
|
|
68
|
+
expect(states.length).toBe(2)
|
|
69
|
+
expect(states[0]).toEqual({
|
|
70
|
+
data: undefined,
|
|
71
|
+
dataUpdatedAt: 0,
|
|
72
|
+
error: null,
|
|
73
|
+
errorUpdatedAt: 0,
|
|
74
|
+
failureCount: 0,
|
|
75
|
+
errorUpdateCount: 0,
|
|
76
|
+
fetchNextPage: expect.any(Function),
|
|
77
|
+
fetchPreviousPage: expect.any(Function),
|
|
78
|
+
hasNextPage: undefined,
|
|
79
|
+
hasPreviousPage: undefined,
|
|
80
|
+
isError: false,
|
|
81
|
+
isFetched: false,
|
|
82
|
+
isFetchedAfterMount: false,
|
|
83
|
+
isFetching: true,
|
|
84
|
+
isPaused: false,
|
|
85
|
+
isFetchingNextPage: false,
|
|
86
|
+
isFetchingPreviousPage: false,
|
|
87
|
+
isLoading: true,
|
|
88
|
+
isLoadingError: false,
|
|
89
|
+
isPlaceholderData: false,
|
|
90
|
+
isPreviousData: false,
|
|
91
|
+
isRefetchError: false,
|
|
92
|
+
isRefetching: false,
|
|
93
|
+
isStale: true,
|
|
94
|
+
isSuccess: false,
|
|
95
|
+
refetch: expect.any(Function),
|
|
96
|
+
remove: expect.any(Function),
|
|
97
|
+
status: 'loading',
|
|
98
|
+
fetchStatus: 'fetching',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
expect(states[1]).toEqual({
|
|
102
|
+
data: { pages: [0], pageParams: [undefined] },
|
|
103
|
+
dataUpdatedAt: expect.any(Number),
|
|
104
|
+
error: null,
|
|
105
|
+
errorUpdatedAt: 0,
|
|
106
|
+
failureCount: 0,
|
|
107
|
+
errorUpdateCount: 0,
|
|
108
|
+
fetchNextPage: expect.any(Function),
|
|
109
|
+
fetchPreviousPage: expect.any(Function),
|
|
110
|
+
hasNextPage: true,
|
|
111
|
+
hasPreviousPage: undefined,
|
|
112
|
+
isError: false,
|
|
113
|
+
isFetched: true,
|
|
114
|
+
isFetchedAfterMount: true,
|
|
115
|
+
isFetching: false,
|
|
116
|
+
isPaused: false,
|
|
117
|
+
isFetchingNextPage: false,
|
|
118
|
+
isFetchingPreviousPage: false,
|
|
119
|
+
isLoading: false,
|
|
120
|
+
isLoadingError: false,
|
|
121
|
+
isPlaceholderData: false,
|
|
122
|
+
isPreviousData: false,
|
|
123
|
+
isRefetchError: false,
|
|
124
|
+
isRefetching: false,
|
|
125
|
+
isStale: true,
|
|
126
|
+
isSuccess: true,
|
|
127
|
+
refetch: expect.any(Function),
|
|
128
|
+
remove: expect.any(Function),
|
|
129
|
+
status: 'success',
|
|
130
|
+
fetchStatus: 'idle',
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should not throw when fetchNextPage returns an error', async () => {
|
|
135
|
+
const key = queryKey()
|
|
136
|
+
let noThrow: boolean
|
|
137
|
+
|
|
138
|
+
function Page() {
|
|
139
|
+
const start = 1
|
|
140
|
+
const state = useInfiniteQuery(
|
|
141
|
+
key,
|
|
142
|
+
async ({ pageParam = start }) => {
|
|
143
|
+
if (pageParam === 2) {
|
|
144
|
+
throw new Error('error')
|
|
145
|
+
}
|
|
146
|
+
return Number(pageParam)
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
retry: 1,
|
|
150
|
+
retryDelay: 10,
|
|
151
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const { fetchNextPage } = state
|
|
156
|
+
|
|
157
|
+
React.useEffect(() => {
|
|
158
|
+
setActTimeout(() => {
|
|
159
|
+
fetchNextPage()
|
|
160
|
+
.then(() => {
|
|
161
|
+
noThrow = true
|
|
162
|
+
})
|
|
163
|
+
.catch(() => undefined)
|
|
164
|
+
}, 20)
|
|
165
|
+
}, [fetchNextPage])
|
|
166
|
+
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
renderWithClient(queryClient, <Page />)
|
|
171
|
+
|
|
172
|
+
await waitFor(() => expect(noThrow).toBe(true))
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should keep the previous data when keepPreviousData is set', async () => {
|
|
176
|
+
const key = queryKey()
|
|
177
|
+
const states: UseInfiniteQueryResult<string>[] = []
|
|
178
|
+
|
|
179
|
+
function Page() {
|
|
180
|
+
const [order, setOrder] = React.useState('desc')
|
|
181
|
+
|
|
182
|
+
const state = useInfiniteQuery(
|
|
183
|
+
[key, order],
|
|
184
|
+
async ({ pageParam = 0 }) => {
|
|
185
|
+
await sleep(10)
|
|
186
|
+
return `${pageParam}-${order}`
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
getNextPageParam: () => 1,
|
|
190
|
+
keepPreviousData: true,
|
|
191
|
+
notifyOnChangeProps: 'all',
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
states.push(state)
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div>
|
|
199
|
+
<button onClick={() => state.fetchNextPage()}>fetchNextPage</button>
|
|
200
|
+
<button onClick={() => setOrder('asc')}>order</button>
|
|
201
|
+
<div>data: {state.data?.pages.join(',') ?? 'null'}</div>
|
|
202
|
+
<div>isFetching: {String(state.isFetching)}</div>
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
208
|
+
|
|
209
|
+
await waitFor(() => rendered.getByText('data: 0-desc'))
|
|
210
|
+
fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
|
|
211
|
+
|
|
212
|
+
await waitFor(() => rendered.getByText('data: 0-desc,1-desc'))
|
|
213
|
+
fireEvent.click(rendered.getByRole('button', { name: /order/i }))
|
|
214
|
+
|
|
215
|
+
await waitFor(() => rendered.getByText('data: 0-asc'))
|
|
216
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
217
|
+
await waitFor(() => expect(states.length).toBe(7))
|
|
218
|
+
|
|
219
|
+
expect(states[0]).toMatchObject({
|
|
220
|
+
data: undefined,
|
|
221
|
+
isFetching: true,
|
|
222
|
+
isFetchingNextPage: false,
|
|
223
|
+
isSuccess: false,
|
|
224
|
+
isPreviousData: false,
|
|
225
|
+
})
|
|
226
|
+
expect(states[1]).toMatchObject({
|
|
227
|
+
data: { pages: ['0-desc'] },
|
|
228
|
+
isFetching: false,
|
|
229
|
+
isFetchingNextPage: false,
|
|
230
|
+
isSuccess: true,
|
|
231
|
+
isPreviousData: false,
|
|
232
|
+
})
|
|
233
|
+
expect(states[2]).toMatchObject({
|
|
234
|
+
data: { pages: ['0-desc'] },
|
|
235
|
+
isFetching: true,
|
|
236
|
+
isFetchingNextPage: true,
|
|
237
|
+
isSuccess: true,
|
|
238
|
+
isPreviousData: false,
|
|
239
|
+
})
|
|
240
|
+
expect(states[3]).toMatchObject({
|
|
241
|
+
data: { pages: ['0-desc', '1-desc'] },
|
|
242
|
+
isFetching: false,
|
|
243
|
+
isFetchingNextPage: false,
|
|
244
|
+
isSuccess: true,
|
|
245
|
+
isPreviousData: false,
|
|
246
|
+
})
|
|
247
|
+
// Set state
|
|
248
|
+
expect(states[4]).toMatchObject({
|
|
249
|
+
data: { pages: ['0-desc', '1-desc'] },
|
|
250
|
+
isFetching: true,
|
|
251
|
+
isFetchingNextPage: false,
|
|
252
|
+
isSuccess: true,
|
|
253
|
+
isPreviousData: true,
|
|
254
|
+
})
|
|
255
|
+
// Hook state update
|
|
256
|
+
expect(states[5]).toMatchObject({
|
|
257
|
+
data: { pages: ['0-desc', '1-desc'] },
|
|
258
|
+
isFetching: true,
|
|
259
|
+
isFetchingNextPage: false,
|
|
260
|
+
isSuccess: true,
|
|
261
|
+
isPreviousData: true,
|
|
262
|
+
})
|
|
263
|
+
expect(states[6]).toMatchObject({
|
|
264
|
+
data: { pages: ['0-asc'] },
|
|
265
|
+
isFetching: false,
|
|
266
|
+
isFetchingNextPage: false,
|
|
267
|
+
isSuccess: true,
|
|
268
|
+
isPreviousData: false,
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should be able to select a part of the data', async () => {
|
|
273
|
+
const key = queryKey()
|
|
274
|
+
const states: UseInfiniteQueryResult<string>[] = []
|
|
275
|
+
|
|
276
|
+
function Page() {
|
|
277
|
+
const state = useInfiniteQuery(key, () => ({ count: 1 }), {
|
|
278
|
+
select: (data) => ({
|
|
279
|
+
pages: data.pages.map((x) => `count: ${x.count}`),
|
|
280
|
+
pageParams: data.pageParams,
|
|
281
|
+
}),
|
|
282
|
+
})
|
|
283
|
+
states.push(state)
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
renderWithClient(queryClient, <Page />)
|
|
288
|
+
|
|
289
|
+
await sleep(10)
|
|
290
|
+
|
|
291
|
+
expect(states.length).toBe(2)
|
|
292
|
+
expect(states[0]).toMatchObject({
|
|
293
|
+
data: undefined,
|
|
294
|
+
isSuccess: false,
|
|
295
|
+
})
|
|
296
|
+
expect(states[1]).toMatchObject({
|
|
297
|
+
data: { pages: ['count: 1'] },
|
|
298
|
+
isSuccess: true,
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should be able to select a new result and not cause infinite renders', async () => {
|
|
303
|
+
const key = queryKey()
|
|
304
|
+
const states: UseInfiniteQueryResult<{ count: number; id: number }>[] = []
|
|
305
|
+
let selectCalled = 0
|
|
306
|
+
|
|
307
|
+
function Page() {
|
|
308
|
+
const state = useInfiniteQuery(key, () => ({ count: 1 }), {
|
|
309
|
+
select: React.useCallback((data: InfiniteData<{ count: number }>) => {
|
|
310
|
+
selectCalled++
|
|
311
|
+
return {
|
|
312
|
+
pages: data.pages.map((x) => ({ ...x, id: Math.random() })),
|
|
313
|
+
pageParams: data.pageParams,
|
|
314
|
+
}
|
|
315
|
+
}, []),
|
|
316
|
+
})
|
|
317
|
+
states.push(state)
|
|
318
|
+
return null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
renderWithClient(queryClient, <Page />)
|
|
322
|
+
|
|
323
|
+
await sleep(20)
|
|
324
|
+
|
|
325
|
+
expect(states.length).toBe(2)
|
|
326
|
+
expect(selectCalled).toBe(1)
|
|
327
|
+
expect(states[0]).toMatchObject({
|
|
328
|
+
data: undefined,
|
|
329
|
+
isSuccess: false,
|
|
330
|
+
})
|
|
331
|
+
expect(states[1]).toMatchObject({
|
|
332
|
+
data: { pages: [{ count: 1 }] },
|
|
333
|
+
isSuccess: true,
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should be able to reverse the data', async () => {
|
|
338
|
+
const key = queryKey()
|
|
339
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
340
|
+
|
|
341
|
+
function Page() {
|
|
342
|
+
const state = useInfiniteQuery(
|
|
343
|
+
key,
|
|
344
|
+
async ({ pageParam = 0 }) => {
|
|
345
|
+
await sleep(10)
|
|
346
|
+
return Number(pageParam)
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
select: (data) => ({
|
|
350
|
+
pages: [...data.pages].reverse(),
|
|
351
|
+
pageParams: [...data.pageParams].reverse(),
|
|
352
|
+
}),
|
|
353
|
+
notifyOnChangeProps: 'all',
|
|
354
|
+
},
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
states.push(state)
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div>
|
|
361
|
+
<button onClick={() => state.fetchNextPage({ pageParam: 1 })}>
|
|
362
|
+
fetchNextPage
|
|
363
|
+
</button>
|
|
364
|
+
<div>data: {state.data?.pages.join(',') ?? 'null'}</div>
|
|
365
|
+
<div>isFetching: {state.isFetching}</div>
|
|
366
|
+
</div>
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
371
|
+
|
|
372
|
+
await waitFor(() => rendered.getByText('data: 0'))
|
|
373
|
+
fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
|
|
374
|
+
|
|
375
|
+
await waitFor(() => rendered.getByText('data: 1,0'))
|
|
376
|
+
|
|
377
|
+
await waitFor(() => expect(states.length).toBe(4))
|
|
378
|
+
expect(states[0]).toMatchObject({
|
|
379
|
+
data: undefined,
|
|
380
|
+
isSuccess: false,
|
|
381
|
+
})
|
|
382
|
+
expect(states[1]).toMatchObject({
|
|
383
|
+
data: { pages: [0] },
|
|
384
|
+
isSuccess: true,
|
|
385
|
+
})
|
|
386
|
+
expect(states[2]).toMatchObject({
|
|
387
|
+
data: { pages: [0] },
|
|
388
|
+
isSuccess: true,
|
|
389
|
+
})
|
|
390
|
+
expect(states[3]).toMatchObject({
|
|
391
|
+
data: { pages: [1, 0] },
|
|
392
|
+
isSuccess: true,
|
|
393
|
+
})
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('should be able to fetch a previous page', async () => {
|
|
397
|
+
const key = queryKey()
|
|
398
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
399
|
+
|
|
400
|
+
function Page() {
|
|
401
|
+
const start = 10
|
|
402
|
+
const state = useInfiniteQuery(
|
|
403
|
+
key,
|
|
404
|
+
async ({ pageParam = start }) => {
|
|
405
|
+
await sleep(10)
|
|
406
|
+
return Number(pageParam)
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
getPreviousPageParam: (firstPage) => firstPage - 1,
|
|
410
|
+
notifyOnChangeProps: 'all',
|
|
411
|
+
},
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
states.push(state)
|
|
415
|
+
|
|
416
|
+
const { fetchPreviousPage } = state
|
|
417
|
+
|
|
418
|
+
React.useEffect(() => {
|
|
419
|
+
setActTimeout(() => {
|
|
420
|
+
fetchPreviousPage()
|
|
421
|
+
}, 20)
|
|
422
|
+
}, [fetchPreviousPage])
|
|
423
|
+
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
renderWithClient(queryClient, <Page />)
|
|
428
|
+
|
|
429
|
+
await sleep(100)
|
|
430
|
+
|
|
431
|
+
expect(states.length).toBe(4)
|
|
432
|
+
expect(states[0]).toMatchObject({
|
|
433
|
+
data: undefined,
|
|
434
|
+
hasNextPage: undefined,
|
|
435
|
+
hasPreviousPage: undefined,
|
|
436
|
+
isFetching: true,
|
|
437
|
+
isFetchingNextPage: false,
|
|
438
|
+
isFetchingPreviousPage: false,
|
|
439
|
+
isSuccess: false,
|
|
440
|
+
})
|
|
441
|
+
expect(states[1]).toMatchObject({
|
|
442
|
+
data: { pages: [10] },
|
|
443
|
+
hasNextPage: undefined,
|
|
444
|
+
hasPreviousPage: true,
|
|
445
|
+
isFetching: false,
|
|
446
|
+
isFetchingNextPage: false,
|
|
447
|
+
isFetchingPreviousPage: false,
|
|
448
|
+
isSuccess: true,
|
|
449
|
+
})
|
|
450
|
+
expect(states[2]).toMatchObject({
|
|
451
|
+
data: { pages: [10] },
|
|
452
|
+
hasNextPage: undefined,
|
|
453
|
+
hasPreviousPage: true,
|
|
454
|
+
isFetching: true,
|
|
455
|
+
isFetchingNextPage: false,
|
|
456
|
+
isFetchingPreviousPage: true,
|
|
457
|
+
isSuccess: true,
|
|
458
|
+
})
|
|
459
|
+
expect(states[3]).toMatchObject({
|
|
460
|
+
data: { pages: [9, 10] },
|
|
461
|
+
hasNextPage: undefined,
|
|
462
|
+
hasPreviousPage: true,
|
|
463
|
+
isFetching: false,
|
|
464
|
+
isFetchingNextPage: false,
|
|
465
|
+
isFetchingPreviousPage: false,
|
|
466
|
+
isSuccess: true,
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('should be able to refetch when providing page params manually', async () => {
|
|
471
|
+
const key = queryKey()
|
|
472
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
473
|
+
|
|
474
|
+
function Page() {
|
|
475
|
+
const state = useInfiniteQuery(key, async ({ pageParam = 10 }) => {
|
|
476
|
+
await sleep(10)
|
|
477
|
+
return Number(pageParam)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
states.push(state)
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<div>
|
|
484
|
+
<button onClick={() => state.fetchNextPage({ pageParam: 11 })}>
|
|
485
|
+
fetchNextPage
|
|
486
|
+
</button>
|
|
487
|
+
<button onClick={() => state.fetchPreviousPage({ pageParam: 9 })}>
|
|
488
|
+
fetchPreviousPage
|
|
489
|
+
</button>
|
|
490
|
+
<button onClick={() => state.refetch()}>refetch</button>
|
|
491
|
+
<div>data: {state.data?.pages.join(',') ?? 'null'}</div>
|
|
492
|
+
<div>isFetching: {String(state.isFetching)}</div>
|
|
493
|
+
</div>
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
498
|
+
|
|
499
|
+
await waitFor(() => rendered.getByText('data: 10'))
|
|
500
|
+
fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
|
|
501
|
+
|
|
502
|
+
await waitFor(() => rendered.getByText('data: 10,11'))
|
|
503
|
+
fireEvent.click(
|
|
504
|
+
rendered.getByRole('button', { name: /fetchPreviousPage/i }),
|
|
505
|
+
)
|
|
506
|
+
await waitFor(() => rendered.getByText('data: 9,10,11'))
|
|
507
|
+
fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
|
|
508
|
+
|
|
509
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
510
|
+
await waitFor(() => expect(states.length).toBe(8))
|
|
511
|
+
|
|
512
|
+
// Initial fetch
|
|
513
|
+
expect(states[0]).toMatchObject({
|
|
514
|
+
data: undefined,
|
|
515
|
+
isFetching: true,
|
|
516
|
+
isFetchingNextPage: false,
|
|
517
|
+
})
|
|
518
|
+
// Initial fetch done
|
|
519
|
+
expect(states[1]).toMatchObject({
|
|
520
|
+
data: { pages: [10] },
|
|
521
|
+
isFetching: false,
|
|
522
|
+
isFetchingNextPage: false,
|
|
523
|
+
})
|
|
524
|
+
// Fetch next page
|
|
525
|
+
expect(states[2]).toMatchObject({
|
|
526
|
+
data: { pages: [10] },
|
|
527
|
+
isFetching: true,
|
|
528
|
+
isFetchingNextPage: true,
|
|
529
|
+
})
|
|
530
|
+
// Fetch next page done
|
|
531
|
+
expect(states[3]).toMatchObject({
|
|
532
|
+
data: { pages: [10, 11] },
|
|
533
|
+
isFetching: false,
|
|
534
|
+
isFetchingNextPage: false,
|
|
535
|
+
})
|
|
536
|
+
// Fetch previous page
|
|
537
|
+
expect(states[4]).toMatchObject({
|
|
538
|
+
data: { pages: [10, 11] },
|
|
539
|
+
isFetching: true,
|
|
540
|
+
isFetchingNextPage: false,
|
|
541
|
+
isFetchingPreviousPage: true,
|
|
542
|
+
})
|
|
543
|
+
// Fetch previous page done
|
|
544
|
+
expect(states[5]).toMatchObject({
|
|
545
|
+
data: { pages: [9, 10, 11] },
|
|
546
|
+
isFetching: false,
|
|
547
|
+
isFetchingNextPage: false,
|
|
548
|
+
isFetchingPreviousPage: false,
|
|
549
|
+
})
|
|
550
|
+
// Refetch
|
|
551
|
+
expect(states[6]).toMatchObject({
|
|
552
|
+
data: { pages: [9, 10, 11] },
|
|
553
|
+
isFetching: true,
|
|
554
|
+
isFetchingNextPage: false,
|
|
555
|
+
isFetchingPreviousPage: false,
|
|
556
|
+
})
|
|
557
|
+
// Refetch done
|
|
558
|
+
expect(states[7]).toMatchObject({
|
|
559
|
+
data: { pages: [9, 10, 11] },
|
|
560
|
+
isFetching: false,
|
|
561
|
+
isFetchingNextPage: false,
|
|
562
|
+
isFetchingPreviousPage: false,
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
it('should be able to refetch when providing page params automatically', async () => {
|
|
567
|
+
const key = queryKey()
|
|
568
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
569
|
+
|
|
570
|
+
function Page() {
|
|
571
|
+
const state = useInfiniteQuery(
|
|
572
|
+
key,
|
|
573
|
+
async ({ pageParam = 10 }) => {
|
|
574
|
+
await sleep(10)
|
|
575
|
+
return Number(pageParam)
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
getPreviousPageParam: (firstPage) => firstPage - 1,
|
|
579
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
580
|
+
notifyOnChangeProps: 'all',
|
|
581
|
+
},
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
states.push(state)
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
<div>
|
|
588
|
+
<button onClick={() => state.fetchNextPage()}>fetchNextPage</button>
|
|
589
|
+
<button onClick={() => state.fetchPreviousPage()}>
|
|
590
|
+
fetchPreviousPage
|
|
591
|
+
</button>
|
|
592
|
+
<button onClick={() => state.refetch()}>refetch</button>
|
|
593
|
+
<div>data: {state.data?.pages.join(',') ?? 'null'}</div>
|
|
594
|
+
<div>isFetching: {String(state.isFetching)}</div>
|
|
595
|
+
</div>
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
600
|
+
|
|
601
|
+
await waitFor(() => rendered.getByText('data: 10'))
|
|
602
|
+
fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
|
|
603
|
+
|
|
604
|
+
await waitFor(() => rendered.getByText('data: 10,11'))
|
|
605
|
+
fireEvent.click(
|
|
606
|
+
rendered.getByRole('button', { name: /fetchPreviousPage/i }),
|
|
607
|
+
)
|
|
608
|
+
await waitFor(() => rendered.getByText('data: 9,10,11'))
|
|
609
|
+
fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
|
|
610
|
+
|
|
611
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
612
|
+
await waitFor(() => expect(states.length).toBe(8))
|
|
613
|
+
|
|
614
|
+
// Initial fetch
|
|
615
|
+
expect(states[0]).toMatchObject({
|
|
616
|
+
data: undefined,
|
|
617
|
+
isFetching: true,
|
|
618
|
+
isFetchingNextPage: false,
|
|
619
|
+
})
|
|
620
|
+
// Initial fetch done
|
|
621
|
+
expect(states[1]).toMatchObject({
|
|
622
|
+
data: { pages: [10] },
|
|
623
|
+
isFetching: false,
|
|
624
|
+
isFetchingNextPage: false,
|
|
625
|
+
})
|
|
626
|
+
// Fetch next page
|
|
627
|
+
expect(states[2]).toMatchObject({
|
|
628
|
+
data: { pages: [10] },
|
|
629
|
+
isFetching: true,
|
|
630
|
+
isFetchingNextPage: true,
|
|
631
|
+
})
|
|
632
|
+
// Fetch next page done
|
|
633
|
+
expect(states[3]).toMatchObject({
|
|
634
|
+
data: { pages: [10, 11] },
|
|
635
|
+
isFetching: false,
|
|
636
|
+
isFetchingNextPage: false,
|
|
637
|
+
})
|
|
638
|
+
// Fetch previous page
|
|
639
|
+
expect(states[4]).toMatchObject({
|
|
640
|
+
data: { pages: [10, 11] },
|
|
641
|
+
isFetching: true,
|
|
642
|
+
isFetchingNextPage: false,
|
|
643
|
+
isFetchingPreviousPage: true,
|
|
644
|
+
})
|
|
645
|
+
// Fetch previous page done
|
|
646
|
+
expect(states[5]).toMatchObject({
|
|
647
|
+
data: { pages: [9, 10, 11] },
|
|
648
|
+
isFetching: false,
|
|
649
|
+
isFetchingNextPage: false,
|
|
650
|
+
isFetchingPreviousPage: false,
|
|
651
|
+
})
|
|
652
|
+
// Refetch
|
|
653
|
+
expect(states[6]).toMatchObject({
|
|
654
|
+
data: { pages: [9, 10, 11] },
|
|
655
|
+
isFetching: true,
|
|
656
|
+
isFetchingNextPage: false,
|
|
657
|
+
isFetchingPreviousPage: false,
|
|
658
|
+
})
|
|
659
|
+
// Refetch done
|
|
660
|
+
expect(states[7]).toMatchObject({
|
|
661
|
+
data: { pages: [9, 10, 11] },
|
|
662
|
+
isFetching: false,
|
|
663
|
+
isFetchingNextPage: false,
|
|
664
|
+
isFetchingPreviousPage: false,
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
it('should be able to refetch only specific pages when refetchPages is provided', async () => {
|
|
669
|
+
const key = queryKey()
|
|
670
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
671
|
+
|
|
672
|
+
function Page() {
|
|
673
|
+
const multiplier = React.useRef(1)
|
|
674
|
+
const state = useInfiniteQuery(
|
|
675
|
+
key,
|
|
676
|
+
async ({ pageParam = 10 }) => {
|
|
677
|
+
await sleep(10)
|
|
678
|
+
return Number(pageParam) * multiplier.current
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
682
|
+
notifyOnChangeProps: 'all',
|
|
683
|
+
},
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
states.push(state)
|
|
687
|
+
|
|
688
|
+
return (
|
|
689
|
+
<div>
|
|
690
|
+
<button onClick={() => state.fetchNextPage()}>fetchNextPage</button>
|
|
691
|
+
<button
|
|
692
|
+
onClick={() => {
|
|
693
|
+
multiplier.current = 2
|
|
694
|
+
state.refetch({
|
|
695
|
+
refetchPage: (_, index) => index === 0,
|
|
696
|
+
})
|
|
697
|
+
}}
|
|
698
|
+
>
|
|
699
|
+
refetchPage
|
|
700
|
+
</button>
|
|
701
|
+
<div>data: {state.data?.pages.join(',') ?? 'null'}</div>
|
|
702
|
+
<div>isFetching: {String(state.isFetching)}</div>
|
|
703
|
+
</div>
|
|
704
|
+
)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
708
|
+
|
|
709
|
+
await waitFor(() => rendered.getByText('data: 10'))
|
|
710
|
+
fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
|
|
711
|
+
|
|
712
|
+
await waitFor(() => rendered.getByText('data: 10,11'))
|
|
713
|
+
fireEvent.click(rendered.getByRole('button', { name: /refetchPage/i }))
|
|
714
|
+
|
|
715
|
+
await waitFor(() => rendered.getByText('data: 20,11'))
|
|
716
|
+
await waitFor(() => rendered.getByText('isFetching: false'))
|
|
717
|
+
await waitFor(() => expect(states.length).toBe(6))
|
|
718
|
+
|
|
719
|
+
// Initial fetch
|
|
720
|
+
expect(states[0]).toMatchObject({
|
|
721
|
+
data: undefined,
|
|
722
|
+
isFetching: true,
|
|
723
|
+
isFetchingNextPage: false,
|
|
724
|
+
})
|
|
725
|
+
// Initial fetch done
|
|
726
|
+
expect(states[1]).toMatchObject({
|
|
727
|
+
data: { pages: [10] },
|
|
728
|
+
isFetching: false,
|
|
729
|
+
isFetchingNextPage: false,
|
|
730
|
+
})
|
|
731
|
+
// Fetch next page
|
|
732
|
+
expect(states[2]).toMatchObject({
|
|
733
|
+
data: { pages: [10] },
|
|
734
|
+
isFetching: true,
|
|
735
|
+
isFetchingNextPage: true,
|
|
736
|
+
})
|
|
737
|
+
// Fetch next page done
|
|
738
|
+
expect(states[3]).toMatchObject({
|
|
739
|
+
data: { pages: [10, 11] },
|
|
740
|
+
isFetching: false,
|
|
741
|
+
isFetchingNextPage: false,
|
|
742
|
+
})
|
|
743
|
+
// Refetch
|
|
744
|
+
expect(states[4]).toMatchObject({
|
|
745
|
+
data: { pages: [10, 11] },
|
|
746
|
+
isFetching: true,
|
|
747
|
+
isFetchingNextPage: false,
|
|
748
|
+
})
|
|
749
|
+
// Refetch done, only page one has been refetched and multiplied
|
|
750
|
+
expect(states[5]).toMatchObject({
|
|
751
|
+
data: { pages: [20, 11] },
|
|
752
|
+
isFetching: false,
|
|
753
|
+
isFetchingNextPage: false,
|
|
754
|
+
})
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
it('should silently cancel any ongoing fetch when fetching more', async () => {
|
|
758
|
+
const key = queryKey()
|
|
759
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
760
|
+
|
|
761
|
+
function Page() {
|
|
762
|
+
const start = 10
|
|
763
|
+
const state = useInfiniteQuery(
|
|
764
|
+
key,
|
|
765
|
+
async ({ pageParam = start }) => {
|
|
766
|
+
await sleep(50)
|
|
767
|
+
return Number(pageParam)
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
771
|
+
notifyOnChangeProps: 'all',
|
|
772
|
+
},
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
states.push(state)
|
|
776
|
+
|
|
777
|
+
const { refetch, fetchNextPage } = state
|
|
778
|
+
|
|
779
|
+
React.useEffect(() => {
|
|
780
|
+
setActTimeout(() => {
|
|
781
|
+
refetch()
|
|
782
|
+
}, 100)
|
|
783
|
+
setActTimeout(() => {
|
|
784
|
+
fetchNextPage()
|
|
785
|
+
}, 110)
|
|
786
|
+
}, [fetchNextPage, refetch])
|
|
787
|
+
|
|
788
|
+
return null
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
renderWithClient(queryClient, <Page />)
|
|
792
|
+
|
|
793
|
+
await sleep(300)
|
|
794
|
+
|
|
795
|
+
expect(states.length).toBe(5)
|
|
796
|
+
expect(states[0]).toMatchObject({
|
|
797
|
+
hasNextPage: undefined,
|
|
798
|
+
data: undefined,
|
|
799
|
+
isFetching: true,
|
|
800
|
+
isFetchingNextPage: false,
|
|
801
|
+
isSuccess: false,
|
|
802
|
+
})
|
|
803
|
+
expect(states[1]).toMatchObject({
|
|
804
|
+
hasNextPage: true,
|
|
805
|
+
data: { pages: [10] },
|
|
806
|
+
isFetching: false,
|
|
807
|
+
isFetchingNextPage: false,
|
|
808
|
+
isSuccess: true,
|
|
809
|
+
})
|
|
810
|
+
expect(states[2]).toMatchObject({
|
|
811
|
+
hasNextPage: true,
|
|
812
|
+
data: { pages: [10] },
|
|
813
|
+
isFetching: true,
|
|
814
|
+
isFetchingNextPage: false,
|
|
815
|
+
isSuccess: true,
|
|
816
|
+
})
|
|
817
|
+
expect(states[3]).toMatchObject({
|
|
818
|
+
hasNextPage: true,
|
|
819
|
+
data: { pages: [10] },
|
|
820
|
+
isFetching: true,
|
|
821
|
+
isFetchingNextPage: true,
|
|
822
|
+
isSuccess: true,
|
|
823
|
+
})
|
|
824
|
+
expect(states[4]).toMatchObject({
|
|
825
|
+
hasNextPage: true,
|
|
826
|
+
data: { pages: [10, 11] },
|
|
827
|
+
isFetching: false,
|
|
828
|
+
isFetchingNextPage: false,
|
|
829
|
+
isSuccess: true,
|
|
830
|
+
})
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
it('should silently cancel an ongoing fetchNextPage request when another fetchNextPage is invoked', async () => {
|
|
834
|
+
const key = queryKey()
|
|
835
|
+
const start = 10
|
|
836
|
+
const onAborts: jest.Mock<any, any>[] = []
|
|
837
|
+
const abortListeners: jest.Mock<any, any>[] = []
|
|
838
|
+
const fetchPage = jest.fn<
|
|
839
|
+
Promise<number>,
|
|
840
|
+
[QueryFunctionContext<typeof key, number>]
|
|
841
|
+
>(async ({ pageParam = start, signal }) => {
|
|
842
|
+
if (signal) {
|
|
843
|
+
const onAbort = jest.fn()
|
|
844
|
+
const abortListener = jest.fn()
|
|
845
|
+
onAborts.push(onAbort)
|
|
846
|
+
abortListeners.push(abortListener)
|
|
847
|
+
signal.onabort = onAbort
|
|
848
|
+
signal.addEventListener('abort', abortListener)
|
|
849
|
+
}
|
|
850
|
+
await sleep(50)
|
|
851
|
+
return Number(pageParam)
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
function Page() {
|
|
855
|
+
const { fetchNextPage } = useInfiniteQuery(key, fetchPage, {
|
|
856
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
React.useEffect(() => {
|
|
860
|
+
setActTimeout(() => {
|
|
861
|
+
fetchNextPage()
|
|
862
|
+
}, 100)
|
|
863
|
+
setActTimeout(() => {
|
|
864
|
+
fetchNextPage()
|
|
865
|
+
}, 110)
|
|
866
|
+
}, [fetchNextPage])
|
|
867
|
+
|
|
868
|
+
return null
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
renderWithClient(queryClient, <Page />)
|
|
872
|
+
|
|
873
|
+
await sleep(300)
|
|
874
|
+
|
|
875
|
+
const expectedCallCount = 3
|
|
876
|
+
expect(fetchPage).toBeCalledTimes(expectedCallCount)
|
|
877
|
+
expect(onAborts).toHaveLength(expectedCallCount)
|
|
878
|
+
expect(abortListeners).toHaveLength(expectedCallCount)
|
|
879
|
+
|
|
880
|
+
let callIndex = 0
|
|
881
|
+
const firstCtx = fetchPage.mock.calls[callIndex]![0]
|
|
882
|
+
expect(firstCtx.pageParam).toBeUndefined()
|
|
883
|
+
expect(firstCtx.queryKey).toEqual(key)
|
|
884
|
+
if (typeof AbortSignal === 'function') {
|
|
885
|
+
expect(firstCtx.signal).toBeInstanceOf(AbortSignal)
|
|
886
|
+
expect(firstCtx.signal?.aborted).toBe(false)
|
|
887
|
+
expect(onAborts[callIndex]).not.toHaveBeenCalled()
|
|
888
|
+
expect(abortListeners[callIndex]).not.toHaveBeenCalled()
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
callIndex = 1
|
|
892
|
+
const secondCtx = fetchPage.mock.calls[callIndex]![0]
|
|
893
|
+
expect(secondCtx.pageParam).toBe(11)
|
|
894
|
+
expect(secondCtx.queryKey).toEqual(key)
|
|
895
|
+
if (typeof AbortSignal === 'function') {
|
|
896
|
+
expect(secondCtx.signal).toBeInstanceOf(AbortSignal)
|
|
897
|
+
expect(secondCtx.signal?.aborted).toBe(true)
|
|
898
|
+
expect(onAborts[callIndex]).toHaveBeenCalledTimes(1)
|
|
899
|
+
expect(abortListeners[callIndex]).toHaveBeenCalledTimes(1)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
callIndex = 2
|
|
903
|
+
const thirdCtx = fetchPage.mock.calls[callIndex]![0]
|
|
904
|
+
expect(thirdCtx.pageParam).toBe(11)
|
|
905
|
+
expect(thirdCtx.queryKey).toEqual(key)
|
|
906
|
+
if (typeof AbortSignal === 'function') {
|
|
907
|
+
expect(thirdCtx.signal).toBeInstanceOf(AbortSignal)
|
|
908
|
+
expect(thirdCtx.signal?.aborted).toBe(false)
|
|
909
|
+
expect(onAborts[callIndex]).not.toHaveBeenCalled()
|
|
910
|
+
expect(abortListeners[callIndex]).not.toHaveBeenCalled()
|
|
911
|
+
}
|
|
912
|
+
})
|
|
913
|
+
|
|
914
|
+
it('should not cancel an ongoing fetchNextPage request when another fetchNextPage is invoked if `cancelRefetch: false` is used ', async () => {
|
|
915
|
+
const key = queryKey()
|
|
916
|
+
const start = 10
|
|
917
|
+
const onAborts: jest.Mock<any, any>[] = []
|
|
918
|
+
const abortListeners: jest.Mock<any, any>[] = []
|
|
919
|
+
const fetchPage = jest.fn<
|
|
920
|
+
Promise<number>,
|
|
921
|
+
[QueryFunctionContext<typeof key, number>]
|
|
922
|
+
>(async ({ pageParam = start, signal }) => {
|
|
923
|
+
if (signal) {
|
|
924
|
+
const onAbort = jest.fn()
|
|
925
|
+
const abortListener = jest.fn()
|
|
926
|
+
onAborts.push(onAbort)
|
|
927
|
+
abortListeners.push(abortListener)
|
|
928
|
+
signal.onabort = onAbort
|
|
929
|
+
signal.addEventListener('abort', abortListener)
|
|
930
|
+
}
|
|
931
|
+
await sleep(50)
|
|
932
|
+
return Number(pageParam)
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
function Page() {
|
|
936
|
+
const { fetchNextPage } = useInfiniteQuery(key, fetchPage, {
|
|
937
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
setActTimeout(() => {
|
|
942
|
+
fetchNextPage()
|
|
943
|
+
}, 100)
|
|
944
|
+
setActTimeout(() => {
|
|
945
|
+
fetchNextPage({ cancelRefetch: false })
|
|
946
|
+
}, 110)
|
|
947
|
+
}, [fetchNextPage])
|
|
948
|
+
|
|
949
|
+
return null
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
renderWithClient(queryClient, <Page />)
|
|
953
|
+
|
|
954
|
+
await sleep(300)
|
|
955
|
+
|
|
956
|
+
const expectedCallCount = 2
|
|
957
|
+
expect(fetchPage).toBeCalledTimes(expectedCallCount)
|
|
958
|
+
expect(onAborts).toHaveLength(expectedCallCount)
|
|
959
|
+
expect(abortListeners).toHaveLength(expectedCallCount)
|
|
960
|
+
|
|
961
|
+
let callIndex = 0
|
|
962
|
+
const firstCtx = fetchPage.mock.calls[callIndex]![0]
|
|
963
|
+
expect(firstCtx.pageParam).toBeUndefined()
|
|
964
|
+
expect(firstCtx.queryKey).toEqual(key)
|
|
965
|
+
if (typeof AbortSignal === 'function') {
|
|
966
|
+
expect(firstCtx.signal).toBeInstanceOf(AbortSignal)
|
|
967
|
+
expect(firstCtx.signal?.aborted).toBe(false)
|
|
968
|
+
expect(onAborts[callIndex]).not.toHaveBeenCalled()
|
|
969
|
+
expect(abortListeners[callIndex]).not.toHaveBeenCalled()
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
callIndex = 1
|
|
973
|
+
const secondCtx = fetchPage.mock.calls[callIndex]![0]
|
|
974
|
+
expect(secondCtx.pageParam).toBe(11)
|
|
975
|
+
expect(secondCtx.queryKey).toEqual(key)
|
|
976
|
+
if (typeof AbortSignal === 'function') {
|
|
977
|
+
expect(secondCtx.signal).toBeInstanceOf(AbortSignal)
|
|
978
|
+
expect(secondCtx.signal?.aborted).toBe(false)
|
|
979
|
+
expect(onAborts[callIndex]).not.toHaveBeenCalled()
|
|
980
|
+
expect(abortListeners[callIndex]).not.toHaveBeenCalled()
|
|
981
|
+
}
|
|
982
|
+
})
|
|
983
|
+
|
|
984
|
+
it('should keep fetching first page when not loaded yet and triggering fetch more', async () => {
|
|
985
|
+
const key = queryKey()
|
|
986
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
987
|
+
|
|
988
|
+
function Page() {
|
|
989
|
+
const start = 10
|
|
990
|
+
const state = useInfiniteQuery(
|
|
991
|
+
key,
|
|
992
|
+
async ({ pageParam = start }) => {
|
|
993
|
+
await sleep(50)
|
|
994
|
+
return Number(pageParam)
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
998
|
+
notifyOnChangeProps: 'all',
|
|
999
|
+
},
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
states.push(state)
|
|
1003
|
+
|
|
1004
|
+
const { fetchNextPage } = state
|
|
1005
|
+
|
|
1006
|
+
React.useEffect(() => {
|
|
1007
|
+
setActTimeout(() => {
|
|
1008
|
+
fetchNextPage()
|
|
1009
|
+
}, 10)
|
|
1010
|
+
}, [fetchNextPage])
|
|
1011
|
+
|
|
1012
|
+
return null
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
renderWithClient(queryClient, <Page />)
|
|
1016
|
+
|
|
1017
|
+
await sleep(100)
|
|
1018
|
+
|
|
1019
|
+
expect(states.length).toBe(2)
|
|
1020
|
+
expect(states[0]).toMatchObject({
|
|
1021
|
+
hasNextPage: undefined,
|
|
1022
|
+
data: undefined,
|
|
1023
|
+
isFetching: true,
|
|
1024
|
+
isFetchingNextPage: false,
|
|
1025
|
+
isSuccess: false,
|
|
1026
|
+
})
|
|
1027
|
+
expect(states[1]).toMatchObject({
|
|
1028
|
+
hasNextPage: true,
|
|
1029
|
+
data: { pages: [10] },
|
|
1030
|
+
isFetching: false,
|
|
1031
|
+
isFetchingNextPage: false,
|
|
1032
|
+
isSuccess: true,
|
|
1033
|
+
})
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
it('should stop fetching additional pages when the component is unmounted and AbortSignal is consumed', async () => {
|
|
1037
|
+
const key = queryKey()
|
|
1038
|
+
let fetches = 0
|
|
1039
|
+
|
|
1040
|
+
const initialData = { pages: [1, 2, 3, 4], pageParams: [0, 1, 2, 3] }
|
|
1041
|
+
|
|
1042
|
+
function List() {
|
|
1043
|
+
useInfiniteQuery(
|
|
1044
|
+
key,
|
|
1045
|
+
async ({ pageParam = 0, signal: _ }) => {
|
|
1046
|
+
fetches++
|
|
1047
|
+
await sleep(50)
|
|
1048
|
+
return Number(pageParam) * 10
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
initialData,
|
|
1052
|
+
getNextPageParam: (_, allPages) => {
|
|
1053
|
+
return allPages.length === 4 ? undefined : allPages.length
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
return null
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function Page() {
|
|
1062
|
+
const [show, setShow] = React.useState(true)
|
|
1063
|
+
|
|
1064
|
+
React.useEffect(() => {
|
|
1065
|
+
setActTimeout(() => {
|
|
1066
|
+
setShow(false)
|
|
1067
|
+
}, 75)
|
|
1068
|
+
}, [])
|
|
1069
|
+
|
|
1070
|
+
return show ? <List /> : null
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
renderWithClient(queryClient, <Page />)
|
|
1074
|
+
|
|
1075
|
+
await sleep(300)
|
|
1076
|
+
|
|
1077
|
+
if (typeof AbortSignal === 'function') {
|
|
1078
|
+
expect(fetches).toBe(2)
|
|
1079
|
+
expect(queryClient.getQueryState(key)).toMatchObject({
|
|
1080
|
+
data: initialData,
|
|
1081
|
+
status: 'success',
|
|
1082
|
+
error: null,
|
|
1083
|
+
})
|
|
1084
|
+
} else {
|
|
1085
|
+
// if AbortSignal is not consumed, fetches should not abort
|
|
1086
|
+
expect(fetches).toBe(4)
|
|
1087
|
+
expect(queryClient.getQueryState(key)).toMatchObject({
|
|
1088
|
+
data: { pages: [0, 10, 20, 30], pageParams: [0, 1, 2, 3] },
|
|
1089
|
+
status: 'success',
|
|
1090
|
+
error: null,
|
|
1091
|
+
})
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
it('should be able to override the cursor in the fetchNextPage callback', async () => {
|
|
1096
|
+
const key = queryKey()
|
|
1097
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1098
|
+
|
|
1099
|
+
function Page() {
|
|
1100
|
+
const state = useInfiniteQuery(
|
|
1101
|
+
key,
|
|
1102
|
+
async ({ pageParam = 0 }) => {
|
|
1103
|
+
await sleep(10)
|
|
1104
|
+
return Number(pageParam)
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
1108
|
+
notifyOnChangeProps: 'all',
|
|
1109
|
+
},
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
states.push(state)
|
|
1113
|
+
|
|
1114
|
+
const { fetchNextPage } = state
|
|
1115
|
+
|
|
1116
|
+
React.useEffect(() => {
|
|
1117
|
+
setActTimeout(() => {
|
|
1118
|
+
fetchNextPage({ pageParam: 5 })
|
|
1119
|
+
}, 20)
|
|
1120
|
+
}, [fetchNextPage])
|
|
1121
|
+
|
|
1122
|
+
return null
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
renderWithClient(queryClient, <Page />)
|
|
1126
|
+
|
|
1127
|
+
await sleep(100)
|
|
1128
|
+
|
|
1129
|
+
expect(states.length).toBe(4)
|
|
1130
|
+
expect(states[0]).toMatchObject({
|
|
1131
|
+
hasNextPage: undefined,
|
|
1132
|
+
data: undefined,
|
|
1133
|
+
isFetching: true,
|
|
1134
|
+
isFetchingNextPage: false,
|
|
1135
|
+
isSuccess: false,
|
|
1136
|
+
})
|
|
1137
|
+
expect(states[1]).toMatchObject({
|
|
1138
|
+
hasNextPage: true,
|
|
1139
|
+
data: { pages: [0] },
|
|
1140
|
+
isFetching: false,
|
|
1141
|
+
isFetchingNextPage: false,
|
|
1142
|
+
isSuccess: true,
|
|
1143
|
+
})
|
|
1144
|
+
expect(states[2]).toMatchObject({
|
|
1145
|
+
hasNextPage: true,
|
|
1146
|
+
data: { pages: [0] },
|
|
1147
|
+
isFetching: true,
|
|
1148
|
+
isFetchingNextPage: true,
|
|
1149
|
+
isSuccess: true,
|
|
1150
|
+
})
|
|
1151
|
+
expect(states[3]).toMatchObject({
|
|
1152
|
+
hasNextPage: true,
|
|
1153
|
+
data: { pages: [0, 5] },
|
|
1154
|
+
isFetching: false,
|
|
1155
|
+
isFetchingNextPage: false,
|
|
1156
|
+
isSuccess: true,
|
|
1157
|
+
})
|
|
1158
|
+
})
|
|
1159
|
+
|
|
1160
|
+
it('should be able to set new pages with the query client', async () => {
|
|
1161
|
+
const key = queryKey()
|
|
1162
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1163
|
+
|
|
1164
|
+
function Page() {
|
|
1165
|
+
const [firstPage, setFirstPage] = React.useState(0)
|
|
1166
|
+
|
|
1167
|
+
const state = useInfiniteQuery(
|
|
1168
|
+
key,
|
|
1169
|
+
async ({ pageParam = firstPage }) => {
|
|
1170
|
+
await sleep(10)
|
|
1171
|
+
return Number(pageParam)
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
1175
|
+
notifyOnChangeProps: 'all',
|
|
1176
|
+
},
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
states.push(state)
|
|
1180
|
+
|
|
1181
|
+
const { refetch } = state
|
|
1182
|
+
|
|
1183
|
+
React.useEffect(() => {
|
|
1184
|
+
setActTimeout(() => {
|
|
1185
|
+
queryClient.setQueryData(key, { pages: [7, 8], pageParams: [7, 8] })
|
|
1186
|
+
setFirstPage(7)
|
|
1187
|
+
}, 20)
|
|
1188
|
+
|
|
1189
|
+
setActTimeout(() => {
|
|
1190
|
+
refetch()
|
|
1191
|
+
}, 50)
|
|
1192
|
+
}, [refetch])
|
|
1193
|
+
|
|
1194
|
+
return null
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
renderWithClient(queryClient, <Page />)
|
|
1198
|
+
|
|
1199
|
+
await sleep(100)
|
|
1200
|
+
|
|
1201
|
+
expect(states.length).toBe(5)
|
|
1202
|
+
expect(states[0]).toMatchObject({
|
|
1203
|
+
hasNextPage: undefined,
|
|
1204
|
+
data: undefined,
|
|
1205
|
+
isFetching: true,
|
|
1206
|
+
isFetchingNextPage: false,
|
|
1207
|
+
isSuccess: false,
|
|
1208
|
+
})
|
|
1209
|
+
// After first fetch
|
|
1210
|
+
expect(states[1]).toMatchObject({
|
|
1211
|
+
hasNextPage: true,
|
|
1212
|
+
data: { pages: [0] },
|
|
1213
|
+
isFetching: false,
|
|
1214
|
+
isFetchingNextPage: false,
|
|
1215
|
+
isSuccess: true,
|
|
1216
|
+
})
|
|
1217
|
+
// Set state
|
|
1218
|
+
expect(states[2]).toMatchObject({
|
|
1219
|
+
hasNextPage: true,
|
|
1220
|
+
data: { pages: [7, 8] },
|
|
1221
|
+
isFetching: false,
|
|
1222
|
+
isFetchingNextPage: false,
|
|
1223
|
+
isSuccess: true,
|
|
1224
|
+
})
|
|
1225
|
+
// Refetch
|
|
1226
|
+
expect(states[3]).toMatchObject({
|
|
1227
|
+
hasNextPage: true,
|
|
1228
|
+
data: { pages: [7, 8] },
|
|
1229
|
+
isFetching: true,
|
|
1230
|
+
isFetchingNextPage: false,
|
|
1231
|
+
isSuccess: true,
|
|
1232
|
+
})
|
|
1233
|
+
// Refetch done
|
|
1234
|
+
expect(states[4]).toMatchObject({
|
|
1235
|
+
hasNextPage: true,
|
|
1236
|
+
data: { pages: [7, 8] },
|
|
1237
|
+
isFetching: false,
|
|
1238
|
+
isFetchingNextPage: false,
|
|
1239
|
+
isSuccess: true,
|
|
1240
|
+
})
|
|
1241
|
+
})
|
|
1242
|
+
|
|
1243
|
+
it('should only refetch the first page when initialData is provided', async () => {
|
|
1244
|
+
const key = queryKey()
|
|
1245
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1246
|
+
|
|
1247
|
+
function Page() {
|
|
1248
|
+
const state = useInfiniteQuery(
|
|
1249
|
+
key,
|
|
1250
|
+
async ({ pageParam }): Promise<number> => {
|
|
1251
|
+
await sleep(10)
|
|
1252
|
+
return pageParam
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
initialData: { pages: [1], pageParams: [1] },
|
|
1256
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
1257
|
+
notifyOnChangeProps: 'all',
|
|
1258
|
+
},
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
states.push(state)
|
|
1262
|
+
|
|
1263
|
+
const { fetchNextPage } = state
|
|
1264
|
+
|
|
1265
|
+
React.useEffect(() => {
|
|
1266
|
+
setActTimeout(() => {
|
|
1267
|
+
fetchNextPage()
|
|
1268
|
+
}, 20)
|
|
1269
|
+
}, [fetchNextPage])
|
|
1270
|
+
|
|
1271
|
+
return null
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
renderWithClient(queryClient, <Page />)
|
|
1275
|
+
|
|
1276
|
+
await sleep(100)
|
|
1277
|
+
|
|
1278
|
+
expect(states.length).toBe(4)
|
|
1279
|
+
expect(states[0]).toMatchObject({
|
|
1280
|
+
data: { pages: [1] },
|
|
1281
|
+
hasNextPage: true,
|
|
1282
|
+
isFetching: true,
|
|
1283
|
+
isFetchingNextPage: false,
|
|
1284
|
+
isSuccess: true,
|
|
1285
|
+
})
|
|
1286
|
+
expect(states[1]).toMatchObject({
|
|
1287
|
+
data: { pages: [1] },
|
|
1288
|
+
hasNextPage: true,
|
|
1289
|
+
isFetching: false,
|
|
1290
|
+
isFetchingNextPage: false,
|
|
1291
|
+
isSuccess: true,
|
|
1292
|
+
})
|
|
1293
|
+
expect(states[2]).toMatchObject({
|
|
1294
|
+
data: { pages: [1] },
|
|
1295
|
+
hasNextPage: true,
|
|
1296
|
+
isFetching: true,
|
|
1297
|
+
isFetchingNextPage: true,
|
|
1298
|
+
isSuccess: true,
|
|
1299
|
+
})
|
|
1300
|
+
expect(states[3]).toMatchObject({
|
|
1301
|
+
data: { pages: [1, 2] },
|
|
1302
|
+
hasNextPage: true,
|
|
1303
|
+
isFetching: false,
|
|
1304
|
+
isFetchingNextPage: false,
|
|
1305
|
+
isSuccess: true,
|
|
1306
|
+
})
|
|
1307
|
+
})
|
|
1308
|
+
|
|
1309
|
+
it('should set hasNextPage to false if getNextPageParam returns undefined', async () => {
|
|
1310
|
+
const key = queryKey()
|
|
1311
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1312
|
+
|
|
1313
|
+
function Page() {
|
|
1314
|
+
const state = useInfiniteQuery(
|
|
1315
|
+
key,
|
|
1316
|
+
({ pageParam = 1 }) => Number(pageParam),
|
|
1317
|
+
{
|
|
1318
|
+
getNextPageParam: () => undefined,
|
|
1319
|
+
},
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
states.push(state)
|
|
1323
|
+
|
|
1324
|
+
return null
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
renderWithClient(queryClient, <Page />)
|
|
1328
|
+
|
|
1329
|
+
await sleep(100)
|
|
1330
|
+
|
|
1331
|
+
expect(states.length).toBe(2)
|
|
1332
|
+
expect(states[0]).toMatchObject({
|
|
1333
|
+
data: undefined,
|
|
1334
|
+
hasNextPage: undefined,
|
|
1335
|
+
isFetching: true,
|
|
1336
|
+
isFetchingNextPage: false,
|
|
1337
|
+
isSuccess: false,
|
|
1338
|
+
})
|
|
1339
|
+
expect(states[1]).toMatchObject({
|
|
1340
|
+
data: { pages: [1] },
|
|
1341
|
+
hasNextPage: false,
|
|
1342
|
+
isFetching: false,
|
|
1343
|
+
isFetchingNextPage: false,
|
|
1344
|
+
isSuccess: true,
|
|
1345
|
+
})
|
|
1346
|
+
})
|
|
1347
|
+
|
|
1348
|
+
it('should compute hasNextPage correctly using initialData', async () => {
|
|
1349
|
+
const key = queryKey()
|
|
1350
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1351
|
+
|
|
1352
|
+
function Page() {
|
|
1353
|
+
const state = useInfiniteQuery(
|
|
1354
|
+
key,
|
|
1355
|
+
({ pageParam = 10 }): number => pageParam,
|
|
1356
|
+
{
|
|
1357
|
+
initialData: { pages: [10], pageParams: [undefined] },
|
|
1358
|
+
getNextPageParam: (lastPage) => (lastPage === 10 ? 11 : undefined),
|
|
1359
|
+
},
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
states.push(state)
|
|
1363
|
+
|
|
1364
|
+
return null
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
renderWithClient(queryClient, <Page />)
|
|
1368
|
+
|
|
1369
|
+
await sleep(100)
|
|
1370
|
+
|
|
1371
|
+
expect(states.length).toBe(2)
|
|
1372
|
+
expect(states[0]).toMatchObject({
|
|
1373
|
+
data: { pages: [10] },
|
|
1374
|
+
hasNextPage: true,
|
|
1375
|
+
isFetching: true,
|
|
1376
|
+
isFetchingNextPage: false,
|
|
1377
|
+
isSuccess: true,
|
|
1378
|
+
})
|
|
1379
|
+
expect(states[1]).toMatchObject({
|
|
1380
|
+
data: { pages: [10] },
|
|
1381
|
+
hasNextPage: true,
|
|
1382
|
+
isFetching: false,
|
|
1383
|
+
isFetchingNextPage: false,
|
|
1384
|
+
isSuccess: true,
|
|
1385
|
+
})
|
|
1386
|
+
})
|
|
1387
|
+
|
|
1388
|
+
it('should compute hasNextPage correctly for falsy getFetchMore return value using initialData', async () => {
|
|
1389
|
+
const key = queryKey()
|
|
1390
|
+
const states: UseInfiniteQueryResult<number>[] = []
|
|
1391
|
+
|
|
1392
|
+
function Page() {
|
|
1393
|
+
const state = useInfiniteQuery(
|
|
1394
|
+
key,
|
|
1395
|
+
({ pageParam = 10 }): number => pageParam,
|
|
1396
|
+
{
|
|
1397
|
+
initialData: { pages: [10], pageParams: [undefined] },
|
|
1398
|
+
getNextPageParam: () => undefined,
|
|
1399
|
+
},
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
states.push(state)
|
|
1403
|
+
|
|
1404
|
+
return null
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
renderWithClient(queryClient, <Page />)
|
|
1408
|
+
|
|
1409
|
+
await sleep(100)
|
|
1410
|
+
|
|
1411
|
+
expect(states.length).toBe(2)
|
|
1412
|
+
expect(states[0]).toMatchObject({
|
|
1413
|
+
data: { pages: [10] },
|
|
1414
|
+
hasNextPage: false,
|
|
1415
|
+
isFetching: true,
|
|
1416
|
+
isFetchingNextPage: false,
|
|
1417
|
+
isSuccess: true,
|
|
1418
|
+
})
|
|
1419
|
+
expect(states[1]).toMatchObject({
|
|
1420
|
+
data: { pages: [10] },
|
|
1421
|
+
hasNextPage: false,
|
|
1422
|
+
isFetching: false,
|
|
1423
|
+
isFetchingNextPage: false,
|
|
1424
|
+
isSuccess: true,
|
|
1425
|
+
})
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
it('should not use selected data when computing hasNextPage', async () => {
|
|
1429
|
+
const key = queryKey()
|
|
1430
|
+
const states: UseInfiniteQueryResult<string>[] = []
|
|
1431
|
+
|
|
1432
|
+
function Page() {
|
|
1433
|
+
const state = useInfiniteQuery(
|
|
1434
|
+
key,
|
|
1435
|
+
({ pageParam = 1 }) => Number(pageParam),
|
|
1436
|
+
{
|
|
1437
|
+
getNextPageParam: (lastPage) => (lastPage === 1 ? 2 : false),
|
|
1438
|
+
select: (data) => ({
|
|
1439
|
+
pages: data.pages.map((x) => x.toString()),
|
|
1440
|
+
pageParams: data.pageParams,
|
|
1441
|
+
}),
|
|
1442
|
+
},
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
states.push(state)
|
|
1446
|
+
|
|
1447
|
+
return null
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
renderWithClient(queryClient, <Page />)
|
|
1451
|
+
|
|
1452
|
+
await sleep(100)
|
|
1453
|
+
|
|
1454
|
+
expect(states.length).toBe(2)
|
|
1455
|
+
expect(states[0]).toMatchObject({
|
|
1456
|
+
data: undefined,
|
|
1457
|
+
hasNextPage: undefined,
|
|
1458
|
+
isFetching: true,
|
|
1459
|
+
isFetchingNextPage: false,
|
|
1460
|
+
isSuccess: false,
|
|
1461
|
+
})
|
|
1462
|
+
expect(states[1]).toMatchObject({
|
|
1463
|
+
data: { pages: ['1'] },
|
|
1464
|
+
hasNextPage: true,
|
|
1465
|
+
isFetching: false,
|
|
1466
|
+
isFetchingNextPage: false,
|
|
1467
|
+
isSuccess: true,
|
|
1468
|
+
})
|
|
1469
|
+
})
|
|
1470
|
+
|
|
1471
|
+
it('should build fresh cursors on refetch', async () => {
|
|
1472
|
+
const key = queryKey()
|
|
1473
|
+
|
|
1474
|
+
const genItems = (size: number) =>
|
|
1475
|
+
[...new Array(size)].fill(null).map((_, d) => d)
|
|
1476
|
+
const items = genItems(15)
|
|
1477
|
+
const limit = 3
|
|
1478
|
+
|
|
1479
|
+
const fetchItemsWithLimit = async (cursor = 0, ts: number) => {
|
|
1480
|
+
await sleep(10)
|
|
1481
|
+
return {
|
|
1482
|
+
nextId: cursor + limit,
|
|
1483
|
+
items: items.slice(cursor, cursor + limit),
|
|
1484
|
+
ts,
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function Page() {
|
|
1489
|
+
const fetchCountRef = React.useRef(0)
|
|
1490
|
+
const {
|
|
1491
|
+
status,
|
|
1492
|
+
data,
|
|
1493
|
+
error,
|
|
1494
|
+
isFetchingNextPage,
|
|
1495
|
+
fetchNextPage,
|
|
1496
|
+
hasNextPage,
|
|
1497
|
+
refetch,
|
|
1498
|
+
} = useInfiniteQuery<Result, Error>(
|
|
1499
|
+
key,
|
|
1500
|
+
({ pageParam = 0 }) =>
|
|
1501
|
+
fetchItemsWithLimit(pageParam, fetchCountRef.current++),
|
|
1502
|
+
{
|
|
1503
|
+
getNextPageParam: (lastPage) => lastPage.nextId,
|
|
1504
|
+
},
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
return (
|
|
1508
|
+
<div>
|
|
1509
|
+
<h1>Pagination</h1>
|
|
1510
|
+
{status === 'loading' ? (
|
|
1511
|
+
'Loading...'
|
|
1512
|
+
) : status === 'error' ? (
|
|
1513
|
+
<span>Error: {error.message}</span>
|
|
1514
|
+
) : (
|
|
1515
|
+
<>
|
|
1516
|
+
<div>Data:</div>
|
|
1517
|
+
{data.pages.map((page, i) => (
|
|
1518
|
+
<div key={i}>
|
|
1519
|
+
<div>
|
|
1520
|
+
Page {i}: {page.ts}
|
|
1521
|
+
</div>
|
|
1522
|
+
<div key={i}>
|
|
1523
|
+
{page.items.map((item) => (
|
|
1524
|
+
<p key={item}>Item: {item}</p>
|
|
1525
|
+
))}
|
|
1526
|
+
</div>
|
|
1527
|
+
</div>
|
|
1528
|
+
))}
|
|
1529
|
+
<div>
|
|
1530
|
+
<button
|
|
1531
|
+
onClick={() => fetchNextPage()}
|
|
1532
|
+
disabled={!hasNextPage || Boolean(isFetchingNextPage)}
|
|
1533
|
+
>
|
|
1534
|
+
{isFetchingNextPage
|
|
1535
|
+
? 'Loading more...'
|
|
1536
|
+
: hasNextPage
|
|
1537
|
+
? 'Load More'
|
|
1538
|
+
: 'Nothing more to load'}
|
|
1539
|
+
</button>
|
|
1540
|
+
<button onClick={() => refetch()}>Refetch</button>
|
|
1541
|
+
<button
|
|
1542
|
+
onClick={() => {
|
|
1543
|
+
// Imagine that this mutation happens somewhere else
|
|
1544
|
+
// makes an actual network request
|
|
1545
|
+
// and calls invalidateQueries in an onSuccess
|
|
1546
|
+
items.splice(4, 1)
|
|
1547
|
+
queryClient.invalidateQueries(key)
|
|
1548
|
+
}}
|
|
1549
|
+
>
|
|
1550
|
+
Remove item
|
|
1551
|
+
</button>
|
|
1552
|
+
</div>
|
|
1553
|
+
<div>{!isFetchingNextPage ? 'Background Updating...' : null}</div>
|
|
1554
|
+
</>
|
|
1555
|
+
)}
|
|
1556
|
+
</div>
|
|
1557
|
+
)
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1561
|
+
|
|
1562
|
+
rendered.getByText('Loading...')
|
|
1563
|
+
|
|
1564
|
+
await waitFor(() => rendered.getByText('Item: 2'))
|
|
1565
|
+
await waitFor(() => rendered.getByText('Page 0: 0'))
|
|
1566
|
+
|
|
1567
|
+
fireEvent.click(rendered.getByText('Load More'))
|
|
1568
|
+
|
|
1569
|
+
await waitFor(() => rendered.getByText('Loading more...'))
|
|
1570
|
+
await waitFor(() => rendered.getByText('Item: 5'))
|
|
1571
|
+
await waitFor(() => rendered.getByText('Page 0: 0'))
|
|
1572
|
+
await waitFor(() => rendered.getByText('Page 1: 1'))
|
|
1573
|
+
|
|
1574
|
+
fireEvent.click(rendered.getByText('Load More'))
|
|
1575
|
+
|
|
1576
|
+
await waitFor(() => rendered.getByText('Loading more...'))
|
|
1577
|
+
await waitFor(() => rendered.getByText('Item: 8'))
|
|
1578
|
+
await waitFor(() => rendered.getByText('Page 0: 0'))
|
|
1579
|
+
await waitFor(() => rendered.getByText('Page 1: 1'))
|
|
1580
|
+
await waitFor(() => rendered.getByText('Page 2: 2'))
|
|
1581
|
+
|
|
1582
|
+
fireEvent.click(rendered.getByText('Refetch'))
|
|
1583
|
+
|
|
1584
|
+
await waitFor(() => rendered.getByText('Background Updating...'))
|
|
1585
|
+
await waitFor(() => rendered.getByText('Item: 8'))
|
|
1586
|
+
await waitFor(() => rendered.getByText('Page 0: 3'))
|
|
1587
|
+
await waitFor(() => rendered.getByText('Page 1: 4'))
|
|
1588
|
+
await waitFor(() => rendered.getByText('Page 2: 5'))
|
|
1589
|
+
|
|
1590
|
+
// ensure that Item: 4 is rendered before removing it
|
|
1591
|
+
expect(rendered.queryAllByText('Item: 4')).toHaveLength(1)
|
|
1592
|
+
|
|
1593
|
+
// remove Item: 4
|
|
1594
|
+
fireEvent.click(rendered.getByText('Remove item'))
|
|
1595
|
+
|
|
1596
|
+
await waitFor(() => rendered.getByText('Background Updating...'))
|
|
1597
|
+
// ensure that an additional item is rendered (it means that cursors were properly rebuilt)
|
|
1598
|
+
await waitFor(() => rendered.getByText('Item: 9'))
|
|
1599
|
+
await waitFor(() => rendered.getByText('Page 0: 6'))
|
|
1600
|
+
await waitFor(() => rendered.getByText('Page 1: 7'))
|
|
1601
|
+
await waitFor(() => rendered.getByText('Page 2: 8'))
|
|
1602
|
+
|
|
1603
|
+
// ensure that Item: 4 is no longer rendered
|
|
1604
|
+
expect(rendered.queryAllByText('Item: 4')).toHaveLength(0)
|
|
1605
|
+
})
|
|
1606
|
+
|
|
1607
|
+
it('should compute hasNextPage correctly for falsy getFetchMore return value on refetching', async () => {
|
|
1608
|
+
const key = queryKey()
|
|
1609
|
+
const MAX = 2
|
|
1610
|
+
|
|
1611
|
+
function Page() {
|
|
1612
|
+
const fetchCountRef = React.useRef(0)
|
|
1613
|
+
const [isRemovedLastPage, setIsRemovedLastPage] =
|
|
1614
|
+
React.useState<boolean>(false)
|
|
1615
|
+
const {
|
|
1616
|
+
status,
|
|
1617
|
+
data,
|
|
1618
|
+
error,
|
|
1619
|
+
isFetching,
|
|
1620
|
+
isFetchingNextPage,
|
|
1621
|
+
fetchNextPage,
|
|
1622
|
+
hasNextPage,
|
|
1623
|
+
refetch,
|
|
1624
|
+
} = useInfiniteQuery<Result, Error>(
|
|
1625
|
+
key,
|
|
1626
|
+
({ pageParam = 0 }) =>
|
|
1627
|
+
fetchItems(
|
|
1628
|
+
pageParam,
|
|
1629
|
+
fetchCountRef.current++,
|
|
1630
|
+
pageParam === MAX || (pageParam === MAX - 1 && isRemovedLastPage),
|
|
1631
|
+
),
|
|
1632
|
+
{
|
|
1633
|
+
getNextPageParam: (lastPage) => lastPage.nextId,
|
|
1634
|
+
},
|
|
1635
|
+
)
|
|
1636
|
+
|
|
1637
|
+
return (
|
|
1638
|
+
<div>
|
|
1639
|
+
<h1>Pagination</h1>
|
|
1640
|
+
{status === 'loading' ? (
|
|
1641
|
+
'Loading...'
|
|
1642
|
+
) : status === 'error' ? (
|
|
1643
|
+
<span>Error: {error.message}</span>
|
|
1644
|
+
) : (
|
|
1645
|
+
<>
|
|
1646
|
+
<div>Data:</div>
|
|
1647
|
+
{data.pages.map((page, i) => (
|
|
1648
|
+
<div key={i}>
|
|
1649
|
+
<div>
|
|
1650
|
+
Page {i}: {page.ts}
|
|
1651
|
+
</div>
|
|
1652
|
+
<div key={i}>
|
|
1653
|
+
{page.items.map((item) => (
|
|
1654
|
+
<p key={item}>Item: {item}</p>
|
|
1655
|
+
))}
|
|
1656
|
+
</div>
|
|
1657
|
+
</div>
|
|
1658
|
+
))}
|
|
1659
|
+
<div>
|
|
1660
|
+
<button
|
|
1661
|
+
onClick={() => fetchNextPage()}
|
|
1662
|
+
disabled={!hasNextPage || Boolean(isFetchingNextPage)}
|
|
1663
|
+
>
|
|
1664
|
+
{isFetchingNextPage
|
|
1665
|
+
? 'Loading more...'
|
|
1666
|
+
: hasNextPage
|
|
1667
|
+
? 'Load More'
|
|
1668
|
+
: 'Nothing more to load'}
|
|
1669
|
+
</button>
|
|
1670
|
+
<button onClick={() => refetch()}>Refetch</button>
|
|
1671
|
+
<button onClick={() => setIsRemovedLastPage(true)}>
|
|
1672
|
+
Remove Last Page
|
|
1673
|
+
</button>
|
|
1674
|
+
</div>
|
|
1675
|
+
<div>
|
|
1676
|
+
{isFetching && !isFetchingNextPage
|
|
1677
|
+
? 'Background Updating...'
|
|
1678
|
+
: null}
|
|
1679
|
+
</div>
|
|
1680
|
+
</>
|
|
1681
|
+
)}
|
|
1682
|
+
</div>
|
|
1683
|
+
)
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1687
|
+
|
|
1688
|
+
rendered.getByText('Loading...')
|
|
1689
|
+
|
|
1690
|
+
await waitFor(() => {
|
|
1691
|
+
rendered.getByText('Item: 9')
|
|
1692
|
+
rendered.getByText('Page 0: 0')
|
|
1693
|
+
})
|
|
1694
|
+
|
|
1695
|
+
fireEvent.click(rendered.getByText('Load More'))
|
|
1696
|
+
|
|
1697
|
+
await waitFor(() => rendered.getByText('Loading more...'))
|
|
1698
|
+
|
|
1699
|
+
await waitFor(() => {
|
|
1700
|
+
rendered.getByText('Item: 19')
|
|
1701
|
+
rendered.getByText('Page 0: 0')
|
|
1702
|
+
rendered.getByText('Page 1: 1')
|
|
1703
|
+
})
|
|
1704
|
+
|
|
1705
|
+
fireEvent.click(rendered.getByText('Load More'))
|
|
1706
|
+
|
|
1707
|
+
await waitFor(() => rendered.getByText('Loading more...'))
|
|
1708
|
+
|
|
1709
|
+
await waitFor(() => {
|
|
1710
|
+
rendered.getByText('Item: 29')
|
|
1711
|
+
rendered.getByText('Page 0: 0')
|
|
1712
|
+
rendered.getByText('Page 1: 1')
|
|
1713
|
+
rendered.getByText('Page 2: 2')
|
|
1714
|
+
})
|
|
1715
|
+
|
|
1716
|
+
rendered.getByText('Nothing more to load')
|
|
1717
|
+
|
|
1718
|
+
fireEvent.click(rendered.getByText('Remove Last Page'))
|
|
1719
|
+
|
|
1720
|
+
await sleep(10)
|
|
1721
|
+
|
|
1722
|
+
fireEvent.click(rendered.getByText('Refetch'))
|
|
1723
|
+
|
|
1724
|
+
await waitFor(() => rendered.getByText('Background Updating...'))
|
|
1725
|
+
|
|
1726
|
+
await waitFor(() => {
|
|
1727
|
+
rendered.getByText('Page 0: 3')
|
|
1728
|
+
rendered.getByText('Page 1: 4')
|
|
1729
|
+
})
|
|
1730
|
+
|
|
1731
|
+
expect(rendered.queryByText('Item: 29')).toBeNull()
|
|
1732
|
+
expect(rendered.queryByText('Page 2: 5')).toBeNull()
|
|
1733
|
+
|
|
1734
|
+
rendered.getByText('Nothing more to load')
|
|
1735
|
+
})
|
|
1736
|
+
|
|
1737
|
+
it('should cancel the query function when there are no more subscriptions', async () => {
|
|
1738
|
+
const key = queryKey()
|
|
1739
|
+
let cancelFn: jest.Mock = jest.fn()
|
|
1740
|
+
|
|
1741
|
+
const queryFn = ({ signal }: { signal?: AbortSignal }) => {
|
|
1742
|
+
const promise = new Promise<string>((resolve, reject) => {
|
|
1743
|
+
cancelFn = jest.fn(() => reject('Cancelled'))
|
|
1744
|
+
signal?.addEventListener('abort', cancelFn)
|
|
1745
|
+
sleep(10).then(() => resolve('OK'))
|
|
1746
|
+
})
|
|
1747
|
+
|
|
1748
|
+
return promise
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function Page() {
|
|
1752
|
+
const state = useInfiniteQuery(key, queryFn)
|
|
1753
|
+
return (
|
|
1754
|
+
<div>
|
|
1755
|
+
<h1>Status: {state.status}</h1>
|
|
1756
|
+
</div>
|
|
1757
|
+
)
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
const rendered = renderWithClient(
|
|
1761
|
+
queryClient,
|
|
1762
|
+
<Blink duration={5}>
|
|
1763
|
+
<Page />
|
|
1764
|
+
</Blink>,
|
|
1765
|
+
)
|
|
1766
|
+
|
|
1767
|
+
await waitFor(() => rendered.getByText('off'))
|
|
1768
|
+
|
|
1769
|
+
if (typeof AbortSignal === 'function') {
|
|
1770
|
+
expect(cancelFn).toHaveBeenCalled()
|
|
1771
|
+
}
|
|
1772
|
+
})
|
|
1773
|
+
})
|