@tanstack/react-query 5.0.0-beta.9 → 5.0.0-rc.0

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.
Files changed (113) hide show
  1. package/build/codemods/coverage/clover.xml +2 -2
  2. package/build/codemods/coverage/index.html +1 -1
  3. package/build/legacy/HydrationBoundary.cjs +37 -2
  4. package/build/legacy/HydrationBoundary.cjs.map +1 -1
  5. package/build/legacy/HydrationBoundary.d.cts +3 -1
  6. package/build/legacy/HydrationBoundary.d.ts +3 -1
  7. package/build/legacy/HydrationBoundary.js +37 -2
  8. package/build/legacy/HydrationBoundary.js.map +1 -1
  9. package/build/legacy/index.cjs +3 -0
  10. package/build/legacy/index.cjs.map +1 -1
  11. package/build/legacy/index.d.cts +1 -0
  12. package/build/legacy/index.d.ts +1 -0
  13. package/build/legacy/index.js +2 -0
  14. package/build/legacy/index.js.map +1 -1
  15. package/build/legacy/queryOptions.cjs.map +1 -1
  16. package/build/legacy/queryOptions.d.cts +7 -3
  17. package/build/legacy/queryOptions.d.ts +7 -3
  18. package/build/legacy/queryOptions.js.map +1 -1
  19. package/build/legacy/suspense.cjs +3 -0
  20. package/build/legacy/suspense.cjs.map +1 -1
  21. package/build/legacy/suspense.d.cts +3 -2
  22. package/build/legacy/suspense.d.ts +3 -2
  23. package/build/legacy/suspense.js +2 -0
  24. package/build/legacy/suspense.js.map +1 -1
  25. package/build/legacy/types.cjs.map +1 -1
  26. package/build/legacy/types.d.cts +4 -4
  27. package/build/legacy/types.d.ts +4 -4
  28. package/build/legacy/useQueries.cjs.map +1 -1
  29. package/build/legacy/useQueries.d.cts +4 -4
  30. package/build/legacy/useQueries.d.ts +4 -4
  31. package/build/legacy/useQueries.js.map +1 -1
  32. package/build/legacy/useSuspenseQueries.cjs +47 -0
  33. package/build/legacy/useSuspenseQueries.cjs.map +1 -0
  34. package/build/legacy/useSuspenseQueries.d.cts +66 -0
  35. package/build/legacy/useSuspenseQueries.d.ts +66 -0
  36. package/build/legacy/useSuspenseQueries.js +23 -0
  37. package/build/legacy/useSuspenseQueries.js.map +1 -0
  38. package/build/legacy/useSuspenseQuery.cjs +2 -1
  39. package/build/legacy/useSuspenseQuery.cjs.map +1 -1
  40. package/build/legacy/useSuspenseQuery.js +2 -1
  41. package/build/legacy/useSuspenseQuery.js.map +1 -1
  42. package/build/legacy/utils.cjs.map +1 -1
  43. package/build/legacy/utils.d.cts +1 -1
  44. package/build/legacy/utils.d.ts +1 -1
  45. package/build/legacy/utils.js.map +1 -1
  46. package/build/modern/HydrationBoundary.cjs +37 -2
  47. package/build/modern/HydrationBoundary.cjs.map +1 -1
  48. package/build/modern/HydrationBoundary.d.cts +3 -1
  49. package/build/modern/HydrationBoundary.d.ts +3 -1
  50. package/build/modern/HydrationBoundary.js +37 -2
  51. package/build/modern/HydrationBoundary.js.map +1 -1
  52. package/build/modern/index.cjs +3 -0
  53. package/build/modern/index.cjs.map +1 -1
  54. package/build/modern/index.d.cts +1 -0
  55. package/build/modern/index.d.ts +1 -0
  56. package/build/modern/index.js +2 -0
  57. package/build/modern/index.js.map +1 -1
  58. package/build/modern/queryOptions.cjs.map +1 -1
  59. package/build/modern/queryOptions.d.cts +7 -3
  60. package/build/modern/queryOptions.d.ts +7 -3
  61. package/build/modern/queryOptions.js.map +1 -1
  62. package/build/modern/suspense.cjs +3 -0
  63. package/build/modern/suspense.cjs.map +1 -1
  64. package/build/modern/suspense.d.cts +3 -2
  65. package/build/modern/suspense.d.ts +3 -2
  66. package/build/modern/suspense.js +2 -0
  67. package/build/modern/suspense.js.map +1 -1
  68. package/build/modern/types.cjs.map +1 -1
  69. package/build/modern/types.d.cts +4 -4
  70. package/build/modern/types.d.ts +4 -4
  71. package/build/modern/useQueries.cjs.map +1 -1
  72. package/build/modern/useQueries.d.cts +4 -4
  73. package/build/modern/useQueries.d.ts +4 -4
  74. package/build/modern/useQueries.js.map +1 -1
  75. package/build/modern/useSuspenseQueries.cjs +47 -0
  76. package/build/modern/useSuspenseQueries.cjs.map +1 -0
  77. package/build/modern/useSuspenseQueries.d.cts +66 -0
  78. package/build/modern/useSuspenseQueries.d.ts +66 -0
  79. package/build/modern/useSuspenseQueries.js +23 -0
  80. package/build/modern/useSuspenseQueries.js.map +1 -0
  81. package/build/modern/useSuspenseQuery.cjs +2 -1
  82. package/build/modern/useSuspenseQuery.cjs.map +1 -1
  83. package/build/modern/useSuspenseQuery.js +2 -1
  84. package/build/modern/useSuspenseQuery.js.map +1 -1
  85. package/build/modern/utils.cjs.map +1 -1
  86. package/build/modern/utils.d.cts +1 -1
  87. package/build/modern/utils.d.ts +1 -1
  88. package/build/modern/utils.js.map +1 -1
  89. package/package.json +7 -3
  90. package/src/HydrationBoundary.tsx +78 -8
  91. package/src/__tests__/HydrationBoundary.test.tsx +111 -7
  92. package/src/__tests__/QueryResetErrorBoundary.test.tsx +10 -6
  93. package/src/__tests__/fine-grained-persister.test.tsx +163 -0
  94. package/src/__tests__/queryOptions.types.test.tsx +31 -0
  95. package/src/__tests__/ssr.test.tsx +2 -2
  96. package/src/__tests__/suspense.test.tsx +94 -386
  97. package/src/__tests__/useInfiniteQuery.test.tsx +44 -44
  98. package/src/__tests__/useInfiniteQuery.type.test.tsx +10 -10
  99. package/src/__tests__/useIsFetching.test.tsx +2 -2
  100. package/src/__tests__/useMutation.test.tsx +4 -4
  101. package/src/__tests__/useMutationState.test.tsx +4 -4
  102. package/src/__tests__/useQueries.test.tsx +65 -64
  103. package/src/__tests__/useQuery.test.tsx +142 -141
  104. package/src/__tests__/useQuery.types.test.tsx +34 -0
  105. package/src/__tests__/utils.tsx +0 -7
  106. package/src/index.ts +5 -0
  107. package/src/queryOptions.ts +11 -3
  108. package/src/suspense.ts +12 -0
  109. package/src/types.ts +18 -12
  110. package/src/useQueries.ts +34 -28
  111. package/src/useSuspenseQueries.ts +164 -0
  112. package/src/useSuspenseQuery.ts +2 -1
  113. package/src/utils.ts +1 -1
@@ -5,21 +5,25 @@ import { vi } from 'vitest'
5
5
  import {
6
6
  QueryCache,
7
7
  QueryErrorResetBoundary,
8
- useInfiniteQuery,
9
- useQueries,
10
- useQuery,
11
8
  useQueryErrorResetBoundary,
9
+ useSuspenseInfiniteQuery,
10
+ useSuspenseQueries,
11
+ useSuspenseQuery,
12
12
  } from '..'
13
13
  import { createQueryClient, queryKey, renderWithClient, sleep } from './utils'
14
- import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult } from '..'
14
+ import type {
15
+ InfiniteData,
16
+ UseSuspenseInfiniteQueryResult,
17
+ UseSuspenseQueryResult,
18
+ } from '..'
15
19
 
16
- describe("useQuery's in Suspense mode", () => {
20
+ describe('useSuspenseQuery', () => {
17
21
  const queryCache = new QueryCache()
18
22
  const queryClient = createQueryClient({ queryCache })
19
23
 
20
24
  it('should render the correct amount of times in Suspense mode', async () => {
21
25
  const key = queryKey()
22
- const states: UseQueryResult<number>[] = []
26
+ const states: Array<UseSuspenseQueryResult<number>> = []
23
27
 
24
28
  let count = 0
25
29
  let renders = 0
@@ -29,14 +33,13 @@ describe("useQuery's in Suspense mode", () => {
29
33
 
30
34
  const [stateKey, setStateKey] = React.useState(key)
31
35
 
32
- const state = useQuery({
36
+ const state = useSuspenseQuery({
33
37
  queryKey: stateKey,
34
38
  queryFn: async () => {
35
39
  count++
36
40
  await sleep(10)
37
41
  return count
38
42
  },
39
- suspense: true,
40
43
  })
41
44
 
42
45
  states.push(state)
@@ -69,18 +72,18 @@ describe("useQuery's in Suspense mode", () => {
69
72
 
70
73
  it('should return the correct states for a successful infinite query', async () => {
71
74
  const key = queryKey()
72
- const states: UseInfiniteQueryResult<InfiniteData<number>>[] = []
75
+ const states: Array<UseSuspenseInfiniteQueryResult<InfiniteData<number>>> =
76
+ []
73
77
 
74
78
  function Page() {
75
79
  const [multiplier, setMultiplier] = React.useState(1)
76
- const state = useInfiniteQuery({
80
+ const state = useSuspenseInfiniteQuery({
77
81
  queryKey: [`${key}_${multiplier}`],
78
82
  queryFn: async ({ pageParam }) => {
79
83
  await sleep(10)
80
84
  return Number(pageParam * multiplier)
81
85
  },
82
- suspense: true,
83
- defaultPageParam: 1,
86
+ initialPageParam: 1,
84
87
  getNextPageParam: (lastPage) => lastPage + 1,
85
88
  })
86
89
  states.push(state)
@@ -120,14 +123,14 @@ describe("useQuery's in Suspense mode", () => {
120
123
  it('should not call the queryFn twice when used in Suspense mode', async () => {
121
124
  const key = queryKey()
122
125
 
123
- const queryFn = vi.fn<unknown[], string>()
126
+ const queryFn = vi.fn<Array<unknown>, string>()
124
127
  queryFn.mockImplementation(() => {
125
128
  sleep(10)
126
129
  return 'data'
127
130
  })
128
131
 
129
132
  function Page() {
130
- useQuery({ queryKey: [key], queryFn, suspense: true })
133
+ useSuspenseQuery({ queryKey: [key], queryFn })
131
134
 
132
135
  return <>rendered</>
133
136
  }
@@ -148,13 +151,12 @@ describe("useQuery's in Suspense mode", () => {
148
151
  const key = queryKey()
149
152
 
150
153
  function Page() {
151
- useQuery({
154
+ useSuspenseQuery({
152
155
  queryKey: key,
153
156
  queryFn: () => {
154
157
  sleep(10)
155
158
  return 'data'
156
159
  },
157
- suspense: true,
158
160
  })
159
161
 
160
162
  return <>rendered</>
@@ -200,7 +202,7 @@ describe("useQuery's in Suspense mode", () => {
200
202
  let succeed = false
201
203
 
202
204
  function Page() {
203
- useQuery({
205
+ useSuspenseQuery({
204
206
  queryKey: key,
205
207
  queryFn: async () => {
206
208
  await sleep(10)
@@ -212,7 +214,6 @@ describe("useQuery's in Suspense mode", () => {
212
214
  }
213
215
  },
214
216
  retryDelay: 10,
215
- suspense: true,
216
217
  })
217
218
 
218
219
  return <div>rendered</div>
@@ -272,7 +273,7 @@ describe("useQuery's in Suspense mode", () => {
272
273
  let succeed = false
273
274
 
274
275
  function Page() {
275
- useQuery({
276
+ useSuspenseQuery({
276
277
  queryKey: key,
277
278
  queryFn: async () => {
278
279
  await sleep(10)
@@ -283,7 +284,6 @@ describe("useQuery's in Suspense mode", () => {
283
284
  }
284
285
  },
285
286
  retry: false,
286
- suspense: true,
287
287
  })
288
288
  return <div>rendered</div>
289
289
  }
@@ -332,7 +332,7 @@ describe("useQuery's in Suspense mode", () => {
332
332
  let count = 0
333
333
 
334
334
  function Component() {
335
- const result = useQuery({
335
+ const result = useSuspenseQuery({
336
336
  queryKey: key,
337
337
  queryFn: async () => {
338
338
  await sleep(100)
@@ -340,7 +340,6 @@ describe("useQuery's in Suspense mode", () => {
340
340
  return count
341
341
  },
342
342
  retry: false,
343
- suspense: true,
344
343
  staleTime: 0,
345
344
  })
346
345
  return (
@@ -388,7 +387,7 @@ describe("useQuery's in Suspense mode", () => {
388
387
  const key2 = queryKey()
389
388
 
390
389
  function Component(props: { queryKey: Array<string> }) {
391
- const result = useQuery({
390
+ const result = useSuspenseQuery({
392
391
  queryKey: props.queryKey,
393
392
  queryFn: async () => {
394
393
  await sleep(100)
@@ -396,7 +395,6 @@ describe("useQuery's in Suspense mode", () => {
396
395
  },
397
396
 
398
397
  retry: false,
399
- suspense: true,
400
398
  })
401
399
  return <div>data: {result.data}</div>
402
400
  }
@@ -437,7 +435,7 @@ describe("useQuery's in Suspense mode", () => {
437
435
  let succeed = false
438
436
 
439
437
  function Page() {
440
- useQuery({
438
+ useSuspenseQuery({
441
439
  queryKey: key,
442
440
  queryFn: async () => {
443
441
  await sleep(10)
@@ -448,7 +446,6 @@ describe("useQuery's in Suspense mode", () => {
448
446
  }
449
447
  },
450
448
  retry: false,
451
- suspense: true,
452
449
  })
453
450
  return <div>rendered</div>
454
451
  }
@@ -499,14 +496,13 @@ describe("useQuery's in Suspense mode", () => {
499
496
  const key = queryKey()
500
497
 
501
498
  function Page() {
502
- useQuery({
499
+ useSuspenseQuery({
503
500
  queryKey: key,
504
501
  queryFn: async (): Promise<unknown> => {
505
502
  await sleep(10)
506
503
  throw new Error('Suspense Error a1x')
507
504
  },
508
505
  retry: false,
509
- suspense: true,
510
506
  })
511
507
  return <div>rendered</div>
512
508
  }
@@ -534,171 +530,6 @@ describe("useQuery's in Suspense mode", () => {
534
530
  consoleMock.mockRestore()
535
531
  })
536
532
 
537
- it('should not throw errors to the error boundary when throwOnError: false', async () => {
538
- const key = queryKey()
539
-
540
- function Page() {
541
- useQuery({
542
- queryKey: key,
543
- queryFn: async (): Promise<unknown> => {
544
- await sleep(10)
545
- throw new Error('Suspense Error a2x')
546
- },
547
- retry: false,
548
- suspense: true,
549
- throwOnError: false,
550
- })
551
- return <div>rendered</div>
552
- }
553
-
554
- function App() {
555
- return (
556
- <ErrorBoundary
557
- fallbackRender={() => (
558
- <div>
559
- <div>error boundary</div>
560
- </div>
561
- )}
562
- >
563
- <React.Suspense fallback="Loading...">
564
- <Page />
565
- </React.Suspense>
566
- </ErrorBoundary>
567
- )
568
- }
569
-
570
- const rendered = renderWithClient(queryClient, <App />)
571
-
572
- await waitFor(() => rendered.getByText('Loading...'))
573
- await waitFor(() => rendered.getByText('rendered'))
574
- })
575
-
576
- it('should throw errors to the error boundary when a throwOnError function returns true', async () => {
577
- const consoleMock = vi
578
- .spyOn(console, 'error')
579
- .mockImplementation(() => undefined)
580
- const key = queryKey()
581
-
582
- function Page() {
583
- useQuery({
584
- queryKey: key,
585
- queryFn: async (): Promise<unknown> => {
586
- await sleep(10)
587
- return Promise.reject(new Error('Remote Error'))
588
- },
589
- retry: false,
590
- suspense: true,
591
- throwOnError: (err) => err.message !== 'Local Error',
592
- })
593
- return <div>rendered</div>
594
- }
595
-
596
- function App() {
597
- return (
598
- <ErrorBoundary
599
- fallbackRender={() => (
600
- <div>
601
- <div>error boundary</div>
602
- </div>
603
- )}
604
- >
605
- <React.Suspense fallback="Loading...">
606
- <Page />
607
- </React.Suspense>
608
- </ErrorBoundary>
609
- )
610
- }
611
-
612
- const rendered = renderWithClient(queryClient, <App />)
613
-
614
- await waitFor(() => rendered.getByText('Loading...'))
615
- await waitFor(() => rendered.getByText('error boundary'))
616
- consoleMock.mockRestore()
617
- })
618
-
619
- it('should not throw errors to the error boundary when a throwOnError function returns false', async () => {
620
- const key = queryKey()
621
-
622
- function Page() {
623
- useQuery({
624
- queryKey: key,
625
- queryFn: async (): Promise<unknown> => {
626
- await sleep(10)
627
- return Promise.reject(new Error('Local Error'))
628
- },
629
- retry: false,
630
- suspense: true,
631
- throwOnError: (err) => err.message !== 'Local Error',
632
- })
633
- return <div>rendered</div>
634
- }
635
-
636
- function App() {
637
- return (
638
- <ErrorBoundary
639
- fallbackRender={() => (
640
- <div>
641
- <div>error boundary</div>
642
- </div>
643
- )}
644
- >
645
- <React.Suspense fallback="Loading...">
646
- <Page />
647
- </React.Suspense>
648
- </ErrorBoundary>
649
- )
650
- }
651
-
652
- const rendered = renderWithClient(queryClient, <App />)
653
-
654
- await waitFor(() => rendered.getByText('Loading...'))
655
- await waitFor(() => rendered.getByText('rendered'))
656
- })
657
-
658
- it('should not call the queryFn when not enabled', async () => {
659
- const key = queryKey()
660
-
661
- const queryFn = vi.fn<unknown[], Promise<string>>()
662
- queryFn.mockImplementation(async () => {
663
- await sleep(10)
664
- return '23'
665
- })
666
-
667
- function Page() {
668
- const [enabled, setEnabled] = React.useState(false)
669
- const result = useQuery({
670
- queryKey: [key],
671
- queryFn,
672
- suspense: true,
673
- enabled,
674
- })
675
-
676
- return (
677
- <div>
678
- <button onClick={() => setEnabled(true)}>fire</button>
679
- <h1>{result.data}</h1>
680
- </div>
681
- )
682
- }
683
-
684
- const rendered = renderWithClient(
685
- queryClient,
686
- <React.Suspense fallback="loading">
687
- <Page />
688
- </React.Suspense>,
689
- )
690
-
691
- expect(queryFn).toHaveBeenCalledTimes(0)
692
-
693
- fireEvent.click(rendered.getByRole('button', { name: /fire/i }))
694
-
695
- await waitFor(() => {
696
- expect(rendered.getByRole('heading').textContent).toBe('23')
697
- })
698
-
699
- expect(queryFn).toHaveBeenCalledTimes(1)
700
- })
701
-
702
533
  it('should error catched in error boundary without infinite loop', async () => {
703
534
  const consoleMock = vi
704
535
  .spyOn(console, 'error')
@@ -710,7 +541,7 @@ describe("useQuery's in Suspense mode", () => {
710
541
  function Page() {
711
542
  const [nonce] = React.useState(0)
712
543
  const queryKeys = [`${key}-${succeed}`]
713
- const result = useQuery({
544
+ const result = useSuspenseQuery({
714
545
  queryKey: queryKeys,
715
546
  queryFn: async () => {
716
547
  await sleep(10)
@@ -721,7 +552,6 @@ describe("useQuery's in Suspense mode", () => {
721
552
  }
722
553
  },
723
554
  retry: false,
724
- suspense: true,
725
555
  })
726
556
  return (
727
557
  <div>
@@ -782,7 +612,7 @@ describe("useQuery's in Suspense mode", () => {
782
612
  const [key, rerender] = React.useReducer((x) => x + 1, 0)
783
613
  const queryKeys = [key, succeed]
784
614
 
785
- const result = useQuery({
615
+ const result = useSuspenseQuery({
786
616
  queryKey: queryKeys,
787
617
  queryFn: async () => {
788
618
  await sleep(10)
@@ -793,8 +623,12 @@ describe("useQuery's in Suspense mode", () => {
793
623
  }
794
624
  },
795
625
  retry: false,
796
- suspense: true,
797
626
  })
627
+
628
+ if (result.error) {
629
+ throw result.error
630
+ }
631
+
798
632
  return (
799
633
  <div>
800
634
  <span>rendered</span> <span>{result.data}</span>
@@ -839,77 +673,9 @@ describe("useQuery's in Suspense mode", () => {
839
673
  consoleMock.mockRestore()
840
674
  })
841
675
 
842
- it('should error catched in error boundary without infinite loop when enabled changed', async () => {
843
- const consoleMock = vi
844
- .spyOn(console, 'error')
845
- .mockImplementation(() => undefined)
846
- function Page() {
847
- const queryKeys = '1'
848
- const [enabled, setEnabled] = React.useState(false)
849
-
850
- const result = useQuery<string>({
851
- queryKey: [queryKeys],
852
- queryFn: async () => {
853
- await sleep(10)
854
- throw new Error('Suspense Error Bingo')
855
- },
856
-
857
- retry: false,
858
- suspense: true,
859
- enabled,
860
- })
861
- return (
862
- <div>
863
- <span>rendered</span> <span>{result.data}</span>
864
- <button
865
- aria-label="fail"
866
- onClick={() => {
867
- setEnabled(true)
868
- }}
869
- >
870
- fail
871
- </button>
872
- </div>
873
- )
874
- }
875
-
876
- function App() {
877
- const { reset } = useQueryErrorResetBoundary()
878
- return (
879
- <ErrorBoundary
880
- onReset={reset}
881
- fallbackRender={() => <div>error boundary</div>}
882
- >
883
- <React.Suspense fallback="Loading...">
884
- <Page />
885
- </React.Suspense>
886
- </ErrorBoundary>
887
- )
888
- }
889
-
890
- const rendered = renderWithClient(queryClient, <App />)
891
-
892
- // render empty data with 'rendered' when enabled is false
893
- await waitFor(() => rendered.getByText('rendered'))
894
-
895
- // change enabled to true
896
- fireEvent.click(rendered.getByLabelText('fail'))
897
-
898
- // render pending fallback
899
- await waitFor(() => rendered.getByText('Loading...'))
900
-
901
- // render error boundary fallback (error boundary)
902
- await waitFor(() => rendered.getByText('error boundary'))
903
- expect(consoleMock).toHaveBeenCalledWith(
904
- expect.objectContaining(new Error('Suspense Error Bingo')),
905
- )
906
-
907
- consoleMock.mockRestore()
908
- })
909
-
910
676
  it('should render the correct amount of times in Suspense mode when gcTime is set to 0', async () => {
911
677
  const key = queryKey()
912
- let state: UseQueryResult<number> | null = null
678
+ let state: UseSuspenseQueryResult<number> | null = null
913
679
 
914
680
  let count = 0
915
681
  let renders = 0
@@ -917,14 +683,13 @@ describe("useQuery's in Suspense mode", () => {
917
683
  function Page() {
918
684
  renders++
919
685
 
920
- state = useQuery({
686
+ state = useSuspenseQuery({
921
687
  queryKey: key,
922
688
  queryFn: async () => {
923
689
  count++
924
690
  await sleep(10)
925
691
  return count
926
692
  },
927
- suspense: true,
928
693
  gcTime: 0,
929
694
  })
930
695
 
@@ -952,67 +717,76 @@ describe("useQuery's in Suspense mode", () => {
952
717
  expect(renders).toBe(2)
953
718
  expect(rendered.queryByText('rendered')).not.toBeNull()
954
719
  })
955
- })
956
720
 
957
- describe('useQueries with suspense', () => {
958
- const queryClient = createQueryClient()
959
- it('should suspend all queries in parallel', async () => {
960
- const key1 = queryKey()
961
- const key2 = queryKey()
962
- const results: string[] = []
963
-
964
- function Fallback() {
965
- results.push('loading')
966
- return <div>loading</div>
967
- }
721
+ it('should not throw background errors to the error boundary', async () => {
722
+ const consoleMock = vi
723
+ .spyOn(console, 'error')
724
+ .mockImplementation(() => undefined)
725
+ let succeed = true
726
+ const key = queryKey()
968
727
 
969
728
  function Page() {
970
- const result = useQueries({
971
- queries: [
972
- {
973
- queryKey: key1,
974
- queryFn: async () => {
975
- results.push('1')
976
- await sleep(10)
977
- return '1'
978
- },
979
- suspense: true,
980
- },
981
- {
982
- queryKey: key2,
983
- queryFn: async () => {
984
- results.push('2')
985
- await sleep(20)
986
- return '2'
987
- },
988
- suspense: true,
989
- },
990
- ],
729
+ const result = useSuspenseQuery({
730
+ queryKey: key,
731
+ queryFn: async () => {
732
+ await sleep(10)
733
+ if (!succeed) {
734
+ throw new Error('Suspense Error Bingo')
735
+ } else {
736
+ return 'data'
737
+ }
738
+ },
739
+ retry: false,
991
740
  })
741
+
992
742
  return (
993
743
  <div>
994
- <h1>data: {result.map((it) => it.data ?? 'null').join(',')}</h1>
744
+ <span>
745
+ rendered {result.data} {result.status}
746
+ </span>
747
+ <button onClick={() => result.refetch()}>refetch</button>
995
748
  </div>
996
749
  )
997
750
  }
998
751
 
999
- const rendered = renderWithClient(
1000
- queryClient,
1001
- <React.Suspense fallback={<Fallback />}>
1002
- <Page />
1003
- </React.Suspense>,
1004
- )
752
+ function App() {
753
+ const { reset } = useQueryErrorResetBoundary()
754
+ return (
755
+ <ErrorBoundary
756
+ onReset={reset}
757
+ fallbackRender={() => <div>error boundary</div>}
758
+ >
759
+ <React.Suspense fallback="Loading...">
760
+ <Page />
761
+ </React.Suspense>
762
+ </ErrorBoundary>
763
+ )
764
+ }
1005
765
 
1006
- await waitFor(() => rendered.getByText('loading'))
1007
- await waitFor(() => rendered.getByText('data: 1,2'))
766
+ const rendered = renderWithClient(queryClient, <App />)
1008
767
 
1009
- expect(results).toEqual(['1', '2', 'loading'])
768
+ // render suspense fallback (Loading...)
769
+ await waitFor(() => rendered.getByText('Loading...'))
770
+ // resolve promise -> render Page (rendered)
771
+ await waitFor(() => rendered.getByText('rendered data success'))
772
+
773
+ // change promise result to error
774
+ succeed = false
775
+ // refetch
776
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
777
+ // we are now in error state but still have data to show
778
+ await waitFor(() => rendered.getByText('rendered data error'))
779
+
780
+ consoleMock.mockRestore()
1010
781
  })
782
+ })
1011
783
 
1012
- it('should allow to mix suspense with non-suspense', async () => {
784
+ describe('useSuspenseQueries', () => {
785
+ const queryClient = createQueryClient()
786
+ it('should suspend all queries in parallel', async () => {
1013
787
  const key1 = queryKey()
1014
788
  const key2 = queryKey()
1015
- const results: string[] = []
789
+ const results: Array<string> = []
1016
790
 
1017
791
  function Fallback() {
1018
792
  results.push('loading')
@@ -1020,34 +794,29 @@ describe('useQueries with suspense', () => {
1020
794
  }
1021
795
 
1022
796
  function Page() {
1023
- const result = useQueries({
797
+ const result = useSuspenseQueries({
1024
798
  queries: [
1025
799
  {
1026
800
  queryKey: key1,
1027
801
  queryFn: async () => {
1028
802
  results.push('1')
1029
- await sleep(50)
803
+ await sleep(10)
1030
804
  return '1'
1031
805
  },
1032
- suspense: true,
1033
806
  },
1034
807
  {
1035
808
  queryKey: key2,
1036
809
  queryFn: async () => {
1037
810
  results.push('2')
1038
- await sleep(200)
811
+ await sleep(20)
1039
812
  return '2'
1040
813
  },
1041
- staleTime: 2000,
1042
- suspense: false,
1043
814
  },
1044
815
  ],
1045
816
  })
1046
-
1047
817
  return (
1048
818
  <div>
1049
819
  <h1>data: {result.map((it) => it.data ?? 'null').join(',')}</h1>
1050
- <h2>status: {result.map((it) => it.status).join(',')}</h2>
1051
820
  </div>
1052
821
  )
1053
822
  }
@@ -1058,9 +827,8 @@ describe('useQueries with suspense', () => {
1058
827
  <Page />
1059
828
  </React.Suspense>,
1060
829
  )
830
+
1061
831
  await waitFor(() => rendered.getByText('loading'))
1062
- await waitFor(() => rendered.getByText('status: success,pending'))
1063
- await waitFor(() => rendered.getByText('data: 1,null'))
1064
832
  await waitFor(() => rendered.getByText('data: 1,2'))
1065
833
 
1066
834
  expect(results).toEqual(['1', '2', 'loading'])
@@ -1069,8 +837,8 @@ describe('useQueries with suspense', () => {
1069
837
  it("shouldn't unmount before all promises fetched", async () => {
1070
838
  const key1 = queryKey()
1071
839
  const key2 = queryKey()
1072
- const results: string[] = []
1073
- const refs: number[] = []
840
+ const results: Array<string> = []
841
+ const refs: Array<number> = []
1074
842
 
1075
843
  function Fallback() {
1076
844
  results.push('loading')
@@ -1079,7 +847,7 @@ describe('useQueries with suspense', () => {
1079
847
 
1080
848
  function Page() {
1081
849
  const ref = React.useRef(Math.random())
1082
- const result = useQueries({
850
+ const result = useSuspenseQueries({
1083
851
  queries: [
1084
852
  {
1085
853
  queryKey: key1,
@@ -1089,7 +857,6 @@ describe('useQueries with suspense', () => {
1089
857
  await sleep(10)
1090
858
  return '1'
1091
859
  },
1092
- suspense: true,
1093
860
  },
1094
861
  {
1095
862
  queryKey: key2,
@@ -1099,7 +866,6 @@ describe('useQueries with suspense', () => {
1099
866
  await sleep(20)
1100
867
  return '2'
1101
868
  },
1102
- suspense: true,
1103
869
  },
1104
870
  ],
1105
871
  })
@@ -1121,62 +887,4 @@ describe('useQueries with suspense', () => {
1121
887
  await waitFor(() => rendered.getByText('data: 1,2'))
1122
888
  expect(refs[0]).toBe(refs[1])
1123
889
  })
1124
-
1125
- it('should suspend all queries in parallel - global configuration', async () => {
1126
- const queryClientSuspenseMode = createQueryClient({
1127
- defaultOptions: {
1128
- queries: {
1129
- suspense: true,
1130
- },
1131
- },
1132
- })
1133
- const key1 = queryKey()
1134
- const key2 = queryKey()
1135
- const results: string[] = []
1136
-
1137
- function Fallback() {
1138
- results.push('loading')
1139
- return <div>loading</div>
1140
- }
1141
-
1142
- function Page() {
1143
- const result = useQueries({
1144
- queries: [
1145
- {
1146
- queryKey: key1,
1147
- queryFn: async () => {
1148
- results.push('1')
1149
- await sleep(10)
1150
- return '1'
1151
- },
1152
- },
1153
- {
1154
- queryKey: key2,
1155
- queryFn: async () => {
1156
- results.push('2')
1157
- await sleep(20)
1158
- return '2'
1159
- },
1160
- },
1161
- ],
1162
- })
1163
- return (
1164
- <div>
1165
- <h1>data: {result.map((it) => it.data ?? 'null').join(',')}</h1>
1166
- </div>
1167
- )
1168
- }
1169
-
1170
- const rendered = renderWithClient(
1171
- queryClientSuspenseMode,
1172
- <React.Suspense fallback={<Fallback />}>
1173
- <Page />
1174
- </React.Suspense>,
1175
- )
1176
-
1177
- await waitFor(() => rendered.getByText('loading'))
1178
- await waitFor(() => rendered.getByText('data: 1,2'))
1179
-
1180
- expect(results).toEqual(['1', '2', 'loading'])
1181
- })
1182
890
  })