@tanstack/solid-query 5.59.16 → 5.59.20

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