@tanstack/react-query 4.29.1 → 4.29.3

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.
@@ -3,7 +3,7 @@ import { ErrorBoundary } from 'react-error-boundary'
3
3
  import * as React from 'react'
4
4
 
5
5
  import { createQueryClient, queryKey, renderWithClient, sleep } from './utils'
6
- import { QueryCache, QueryErrorResetBoundary, useQuery } from '..'
6
+ import { QueryCache, QueryErrorResetBoundary, useQueries, useQuery } from '..'
7
7
 
8
8
  // TODO: This should be removed with the types for react-error-boundary get updated.
9
9
  declare module 'react-error-boundary' {
@@ -16,368 +16,628 @@ describe('QueryErrorResetBoundary', () => {
16
16
  const queryCache = new QueryCache()
17
17
  const queryClient = createQueryClient({ queryCache })
18
18
 
19
- it('should retry fetch if the reset error boundary has been reset', async () => {
20
- const key = queryKey()
19
+ describe('useQuery', () => {
20
+ it('should retry fetch if the reset error boundary has been reset', async () => {
21
+ const key = queryKey()
22
+
23
+ let succeed = false
24
+
25
+ function Page() {
26
+ const { data } = useQuery(
27
+ key,
28
+ async () => {
29
+ await sleep(10)
30
+ if (!succeed) {
31
+ throw new Error('Error')
32
+ } else {
33
+ return 'data'
34
+ }
35
+ },
36
+ {
37
+ retry: false,
38
+ useErrorBoundary: true,
39
+ },
40
+ )
41
+ return <div>{data}</div>
42
+ }
43
+
44
+ const rendered = renderWithClient(
45
+ queryClient,
46
+ <QueryErrorResetBoundary>
47
+ {({ reset }) => (
48
+ <ErrorBoundary
49
+ onReset={reset}
50
+ fallbackRender={({ resetErrorBoundary }) => (
51
+ <div>
52
+ <div>error boundary</div>
53
+ <button
54
+ onClick={() => {
55
+ resetErrorBoundary()
56
+ }}
57
+ >
58
+ retry
59
+ </button>
60
+ </div>
61
+ )}
62
+ >
63
+ <Page />
64
+ </ErrorBoundary>
65
+ )}
66
+ </QueryErrorResetBoundary>,
67
+ )
21
68
 
22
- let succeed = false
69
+ await waitFor(() => rendered.getByText('error boundary'))
70
+ await waitFor(() => rendered.getByText('retry'))
71
+ succeed = true
72
+ fireEvent.click(rendered.getByText('retry'))
73
+ await waitFor(() => rendered.getByText('data'))
74
+ })
75
+
76
+ it('should not throw error if query is disabled', async () => {
77
+ const key = queryKey()
78
+
79
+ let succeed = false
80
+
81
+ function Page() {
82
+ const { data, status } = useQuery(
83
+ key,
84
+ async () => {
85
+ await sleep(10)
86
+ if (!succeed) {
87
+ throw new Error('Error')
88
+ } else {
89
+ return 'data'
90
+ }
91
+ },
92
+ {
93
+ retry: false,
94
+ enabled: !succeed,
95
+ useErrorBoundary: true,
96
+ },
97
+ )
98
+ return (
99
+ <div>
100
+ <div>status: {status}</div>
101
+ <div>{data}</div>
102
+ </div>
103
+ )
104
+ }
105
+
106
+ const rendered = renderWithClient(
107
+ queryClient,
108
+ <QueryErrorResetBoundary>
109
+ {({ reset }) => (
110
+ <ErrorBoundary
111
+ onReset={reset}
112
+ fallbackRender={({ resetErrorBoundary }) => (
113
+ <div>
114
+ <div>error boundary</div>
115
+ <button
116
+ onClick={() => {
117
+ resetErrorBoundary()
118
+ }}
119
+ >
120
+ retry
121
+ </button>
122
+ </div>
123
+ )}
124
+ >
125
+ <Page />
126
+ </ErrorBoundary>
127
+ )}
128
+ </QueryErrorResetBoundary>,
129
+ )
23
130
 
24
- function Page() {
25
- const { data } = useQuery(
26
- key,
27
- async () => {
28
- await sleep(10)
29
- if (!succeed) {
30
- throw new Error('Error')
31
- } else {
32
- return 'data'
33
- }
34
- },
35
- {
36
- retry: false,
37
- useErrorBoundary: true,
38
- },
131
+ await waitFor(() => rendered.getByText('error boundary'))
132
+ await waitFor(() => rendered.getByText('retry'))
133
+ succeed = true
134
+ fireEvent.click(rendered.getByText('retry'))
135
+ await waitFor(() => rendered.getByText('status: error'))
136
+ })
137
+
138
+ it('should not throw error if query is disabled, and refetch if query becomes enabled again', async () => {
139
+ const key = queryKey()
140
+
141
+ let succeed = false
142
+
143
+ function Page() {
144
+ const [enabled, setEnabled] = React.useState(false)
145
+ const { data } = useQuery(
146
+ key,
147
+ async () => {
148
+ await sleep(10)
149
+ if (!succeed) {
150
+ throw new Error('Error')
151
+ } else {
152
+ return 'data'
153
+ }
154
+ },
155
+ {
156
+ retry: false,
157
+ enabled,
158
+ useErrorBoundary: true,
159
+ },
160
+ )
161
+
162
+ React.useEffect(() => {
163
+ setEnabled(true)
164
+ }, [])
165
+
166
+ return <div>{data}</div>
167
+ }
168
+
169
+ const rendered = renderWithClient(
170
+ queryClient,
171
+ <QueryErrorResetBoundary>
172
+ {({ reset }) => (
173
+ <ErrorBoundary
174
+ onReset={reset}
175
+ fallbackRender={({ resetErrorBoundary }) => (
176
+ <div>
177
+ <div>error boundary</div>
178
+ <button
179
+ onClick={() => {
180
+ resetErrorBoundary()
181
+ }}
182
+ >
183
+ retry
184
+ </button>
185
+ </div>
186
+ )}
187
+ >
188
+ <Page />
189
+ </ErrorBoundary>
190
+ )}
191
+ </QueryErrorResetBoundary>,
39
192
  )
40
- return <div>{data}</div>
41
- }
42
-
43
- const rendered = renderWithClient(
44
- queryClient,
45
- <QueryErrorResetBoundary>
46
- {({ reset }) => (
47
- <ErrorBoundary
48
- onReset={reset}
49
- fallbackRender={({ resetErrorBoundary }) => (
50
- <div>
51
- <div>error boundary</div>
52
- <button
53
- onClick={() => {
54
- resetErrorBoundary()
55
- }}
56
- >
57
- retry
58
- </button>
59
- </div>
60
- )}
61
- >
62
- <Page />
63
- </ErrorBoundary>
64
- )}
65
- </QueryErrorResetBoundary>,
66
- )
67
-
68
- await waitFor(() => rendered.getByText('error boundary'))
69
- await waitFor(() => rendered.getByText('retry'))
70
- succeed = true
71
- fireEvent.click(rendered.getByText('retry'))
72
- await waitFor(() => rendered.getByText('data'))
73
- })
74
193
 
75
- it('should not throw error if query is disabled', async () => {
76
- const key = queryKey()
194
+ await waitFor(() => rendered.getByText('error boundary'))
195
+ await waitFor(() => rendered.getByText('retry'))
196
+ succeed = true
197
+ fireEvent.click(rendered.getByText('retry'))
198
+ await waitFor(() => rendered.getByText('data'))
199
+ })
77
200
 
78
- let succeed = false
201
+ it('should throw error if query is disabled and manually refetched', async () => {
202
+ const key = queryKey()
79
203
 
80
- function Page() {
81
- const { data, status } = useQuery(
82
- key,
83
- async () => {
84
- await sleep(10)
85
- if (!succeed) {
204
+ function Page() {
205
+ const { data, refetch, status, fetchStatus } = useQuery<string>(
206
+ key,
207
+ async () => {
86
208
  throw new Error('Error')
87
- } else {
88
- return 'data'
89
- }
90
- },
91
- {
92
- retry: false,
93
- enabled: !succeed,
94
- useErrorBoundary: true,
95
- },
209
+ },
210
+ {
211
+ retry: false,
212
+ enabled: false,
213
+ useErrorBoundary: true,
214
+ },
215
+ )
216
+
217
+ return (
218
+ <div>
219
+ <button onClick={() => refetch()}>refetch</button>
220
+ <div>
221
+ status: {status}, fetchStatus: {fetchStatus}
222
+ </div>
223
+ <div>{data}</div>
224
+ </div>
225
+ )
226
+ }
227
+
228
+ const rendered = renderWithClient(
229
+ queryClient,
230
+ <QueryErrorResetBoundary>
231
+ {({ reset }) => (
232
+ <ErrorBoundary
233
+ onReset={reset}
234
+ fallbackRender={({ resetErrorBoundary }) => (
235
+ <div>
236
+ <div>error boundary</div>
237
+ <button
238
+ onClick={() => {
239
+ resetErrorBoundary()
240
+ }}
241
+ >
242
+ retry
243
+ </button>
244
+ </div>
245
+ )}
246
+ >
247
+ <Page />
248
+ </ErrorBoundary>
249
+ )}
250
+ </QueryErrorResetBoundary>,
96
251
  )
97
- return (
98
- <div>
99
- <div>status: {status}</div>
100
- <div>{data}</div>
101
- </div>
252
+
253
+ await waitFor(() =>
254
+ rendered.getByText('status: loading, fetchStatus: idle'),
255
+ )
256
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
257
+ await waitFor(() => rendered.getByText('error boundary'))
258
+ })
259
+
260
+ it('should not retry fetch if the reset error boundary has not been reset', async () => {
261
+ const key = queryKey()
262
+
263
+ let succeed = false
264
+
265
+ function Page() {
266
+ const { data } = useQuery(
267
+ key,
268
+ async () => {
269
+ await sleep(10)
270
+ if (!succeed) {
271
+ throw new Error('Error')
272
+ } else {
273
+ return 'data'
274
+ }
275
+ },
276
+ {
277
+ retry: false,
278
+ useErrorBoundary: true,
279
+ },
280
+ )
281
+ return <div>{data}</div>
282
+ }
283
+
284
+ const rendered = renderWithClient(
285
+ queryClient,
286
+ <QueryErrorResetBoundary>
287
+ {() => (
288
+ <ErrorBoundary
289
+ fallbackRender={({ resetErrorBoundary }) => (
290
+ <div>
291
+ <div>error boundary</div>
292
+ <button
293
+ onClick={() => {
294
+ resetErrorBoundary()
295
+ }}
296
+ >
297
+ retry
298
+ </button>
299
+ </div>
300
+ )}
301
+ >
302
+ <Page />
303
+ </ErrorBoundary>
304
+ )}
305
+ </QueryErrorResetBoundary>,
102
306
  )
103
- }
104
-
105
- const rendered = renderWithClient(
106
- queryClient,
107
- <QueryErrorResetBoundary>
108
- {({ reset }) => (
109
- <ErrorBoundary
110
- onReset={reset}
111
- fallbackRender={({ resetErrorBoundary }) => (
112
- <div>
113
- <div>error boundary</div>
114
- <button
115
- onClick={() => {
116
- resetErrorBoundary()
117
- }}
118
- >
119
- retry
120
- </button>
121
- </div>
122
- )}
123
- >
124
- <Page />
125
- </ErrorBoundary>
126
- )}
127
- </QueryErrorResetBoundary>,
128
- )
129
-
130
- await waitFor(() => rendered.getByText('error boundary'))
131
- await waitFor(() => rendered.getByText('retry'))
132
- succeed = true
133
- fireEvent.click(rendered.getByText('retry'))
134
- await waitFor(() => rendered.getByText('status: error'))
135
- })
136
307
 
137
- it('should not throw error if query is disabled, and refetch if query becomes enabled again', async () => {
138
- const key = queryKey()
308
+ await waitFor(() => rendered.getByText('error boundary'))
309
+ await waitFor(() => rendered.getByText('retry'))
310
+ succeed = true
311
+ fireEvent.click(rendered.getByText('retry'))
312
+ await waitFor(() => rendered.getByText('error boundary'))
313
+ })
314
+
315
+ it('should retry fetch if the reset error boundary has been reset and the query contains data from a previous fetch', async () => {
316
+ const key = queryKey()
317
+
318
+ let succeed = false
319
+
320
+ function Page() {
321
+ const { data } = useQuery(
322
+ key,
323
+ async () => {
324
+ await sleep(10)
325
+ if (!succeed) {
326
+ throw new Error('Error')
327
+ } else {
328
+ return 'data'
329
+ }
330
+ },
331
+ {
332
+ retry: false,
333
+ useErrorBoundary: true,
334
+ initialData: 'initial',
335
+ },
336
+ )
337
+ return <div>{data}</div>
338
+ }
339
+
340
+ const rendered = renderWithClient(
341
+ queryClient,
342
+ <QueryErrorResetBoundary>
343
+ {({ reset }) => (
344
+ <ErrorBoundary
345
+ onReset={reset}
346
+ fallbackRender={({ resetErrorBoundary }) => (
347
+ <div>
348
+ <div>error boundary</div>
349
+ <button
350
+ onClick={() => {
351
+ resetErrorBoundary()
352
+ }}
353
+ >
354
+ retry
355
+ </button>
356
+ </div>
357
+ )}
358
+ >
359
+ <Page />
360
+ </ErrorBoundary>
361
+ )}
362
+ </QueryErrorResetBoundary>,
363
+ )
139
364
 
140
- let succeed = false
365
+ await waitFor(() => rendered.getByText('error boundary'))
366
+ await waitFor(() => rendered.getByText('retry'))
367
+ succeed = true
368
+ fireEvent.click(rendered.getByText('retry'))
369
+ await waitFor(() => rendered.getByText('data'))
370
+ })
371
+
372
+ it('should not retry fetch if the reset error boundary has not been reset after a previous reset', async () => {
373
+ const key = queryKey()
374
+
375
+ let succeed = false
376
+ let shouldReset = true
377
+
378
+ function Page() {
379
+ const { data } = useQuery(
380
+ key,
381
+ async () => {
382
+ await sleep(10)
383
+ if (!succeed) {
384
+ throw new Error('Error')
385
+ } else {
386
+ return 'data'
387
+ }
388
+ },
389
+ {
390
+ retry: false,
391
+ useErrorBoundary: true,
392
+ },
393
+ )
394
+ return <div>{data}</div>
395
+ }
396
+
397
+ const rendered = renderWithClient(
398
+ queryClient,
399
+ <QueryErrorResetBoundary>
400
+ {({ reset }) => (
401
+ <ErrorBoundary
402
+ onReset={() => {
403
+ if (shouldReset) {
404
+ reset()
405
+ }
406
+ }}
407
+ fallbackRender={({ resetErrorBoundary }) => (
408
+ <div>
409
+ <div>error boundary</div>
410
+ <button
411
+ onClick={() => {
412
+ resetErrorBoundary()
413
+ }}
414
+ >
415
+ retry
416
+ </button>
417
+ </div>
418
+ )}
419
+ >
420
+ <Page />
421
+ </ErrorBoundary>
422
+ )}
423
+ </QueryErrorResetBoundary>,
424
+ )
141
425
 
142
- function Page() {
143
- const [enabled, setEnabled] = React.useState(false)
144
- const { data } = useQuery(
145
- key,
146
- async () => {
147
- await sleep(10)
148
- if (!succeed) {
426
+ await waitFor(() => rendered.getByText('error boundary'))
427
+ await waitFor(() => rendered.getByText('retry'))
428
+ shouldReset = true
429
+ fireEvent.click(rendered.getByText('retry'))
430
+ await waitFor(() => rendered.getByText('error boundary'))
431
+ succeed = true
432
+ shouldReset = false
433
+ fireEvent.click(rendered.getByText('retry'))
434
+ await waitFor(() => rendered.getByText('error boundary'))
435
+ })
436
+
437
+ it('should throw again on error after the reset error boundary has been reset', async () => {
438
+ const key = queryKey()
439
+ let fetchCount = 0
440
+
441
+ function Page() {
442
+ const { data } = useQuery<string>(
443
+ key,
444
+ async () => {
445
+ fetchCount++
446
+ await sleep(10)
149
447
  throw new Error('Error')
150
- } else {
151
- return 'data'
152
- }
153
- },
154
- {
155
- retry: false,
156
- enabled,
157
- useErrorBoundary: true,
158
- },
448
+ },
449
+ {
450
+ retry: false,
451
+ useErrorBoundary: true,
452
+ },
453
+ )
454
+ return <div>{data}</div>
455
+ }
456
+
457
+ const rendered = renderWithClient(
458
+ queryClient,
459
+ <QueryErrorResetBoundary>
460
+ {({ reset }) => (
461
+ <ErrorBoundary
462
+ onReset={reset}
463
+ fallbackRender={({ resetErrorBoundary }) => (
464
+ <div>
465
+ <div>error boundary</div>
466
+ <button
467
+ onClick={() => {
468
+ resetErrorBoundary()
469
+ }}
470
+ >
471
+ retry
472
+ </button>
473
+ </div>
474
+ )}
475
+ >
476
+ <Page />
477
+ </ErrorBoundary>
478
+ )}
479
+ </QueryErrorResetBoundary>,
159
480
  )
160
481
 
161
- React.useEffect(() => {
162
- setEnabled(true)
163
- }, [])
164
-
165
- return <div>{data}</div>
166
- }
167
-
168
- const rendered = renderWithClient(
169
- queryClient,
170
- <QueryErrorResetBoundary>
171
- {({ reset }) => (
172
- <ErrorBoundary
173
- onReset={reset}
174
- fallbackRender={({ resetErrorBoundary }) => (
175
- <div>
176
- <div>error boundary</div>
177
- <button
178
- onClick={() => {
179
- resetErrorBoundary()
180
- }}
181
- >
182
- retry
183
- </button>
184
- </div>
185
- )}
186
- >
187
- <Page />
188
- </ErrorBoundary>
189
- )}
190
- </QueryErrorResetBoundary>,
191
- )
192
-
193
- await waitFor(() => rendered.getByText('error boundary'))
194
- await waitFor(() => rendered.getByText('retry'))
195
- succeed = true
196
- fireEvent.click(rendered.getByText('retry'))
197
- await waitFor(() => rendered.getByText('data'))
198
- })
199
-
200
- it('should throw error if query is disabled and manually refetched', async () => {
201
- const key = queryKey()
202
-
203
- function Page() {
204
- const { data, refetch, status, fetchStatus } = useQuery<string>(
205
- key,
206
- async () => {
207
- throw new Error('Error')
208
- },
209
- {
210
- retry: false,
211
- enabled: false,
212
- useErrorBoundary: true,
213
- },
482
+ await waitFor(() => rendered.getByText('error boundary'))
483
+ await waitFor(() => rendered.getByText('retry'))
484
+ fireEvent.click(rendered.getByText('retry'))
485
+ await waitFor(() => rendered.getByText('error boundary'))
486
+ await waitFor(() => rendered.getByText('retry'))
487
+ fireEvent.click(rendered.getByText('retry'))
488
+ await waitFor(() => rendered.getByText('error boundary'))
489
+ expect(fetchCount).toBe(3)
490
+ })
491
+
492
+ it('should never render the component while the query is in error state', async () => {
493
+ const key = queryKey()
494
+ let fetchCount = 0
495
+ let renders = 0
496
+
497
+ function Page() {
498
+ const { data } = useQuery(
499
+ key,
500
+ async () => {
501
+ fetchCount++
502
+ await sleep(10)
503
+ if (fetchCount > 2) {
504
+ return 'data'
505
+ } else {
506
+ throw new Error('Error')
507
+ }
508
+ },
509
+ {
510
+ retry: false,
511
+ suspense: true,
512
+ },
513
+ )
514
+ renders++
515
+ return <div>{data}</div>
516
+ }
517
+
518
+ const rendered = renderWithClient(
519
+ queryClient,
520
+ <QueryErrorResetBoundary>
521
+ {({ reset }) => (
522
+ <ErrorBoundary
523
+ onReset={reset}
524
+ fallbackRender={({ resetErrorBoundary }) => (
525
+ <div>
526
+ <div>error boundary</div>
527
+ <button
528
+ onClick={() => {
529
+ resetErrorBoundary()
530
+ }}
531
+ >
532
+ retry
533
+ </button>
534
+ </div>
535
+ )}
536
+ >
537
+ <React.Suspense fallback={<div>loading</div>}>
538
+ <Page />
539
+ </React.Suspense>
540
+ </ErrorBoundary>
541
+ )}
542
+ </QueryErrorResetBoundary>,
214
543
  )
215
544
 
216
- return (
217
- <div>
218
- <button onClick={() => refetch()}>refetch</button>
545
+ await waitFor(() => rendered.getByText('error boundary'))
546
+ await waitFor(() => rendered.getByText('retry'))
547
+ fireEvent.click(rendered.getByText('retry'))
548
+ await waitFor(() => rendered.getByText('error boundary'))
549
+ await waitFor(() => rendered.getByText('retry'))
550
+ fireEvent.click(rendered.getByText('retry'))
551
+ await waitFor(() => rendered.getByText('data'))
552
+ expect(fetchCount).toBe(3)
553
+ expect(renders).toBe(1)
554
+ })
555
+
556
+ it('should render children', async () => {
557
+ function Page() {
558
+ return (
219
559
  <div>
220
- status: {status}, fetchStatus: {fetchStatus}
560
+ <span>page</span>
221
561
  </div>
222
- <div>{data}</div>
223
- </div>
562
+ )
563
+ }
564
+
565
+ const rendered = renderWithClient(
566
+ queryClient,
567
+ <QueryErrorResetBoundary>
568
+ <Page />
569
+ </QueryErrorResetBoundary>,
224
570
  )
225
- }
226
-
227
- const rendered = renderWithClient(
228
- queryClient,
229
- <QueryErrorResetBoundary>
230
- {({ reset }) => (
231
- <ErrorBoundary
232
- onReset={reset}
233
- fallbackRender={({ resetErrorBoundary }) => (
234
- <div>
235
- <div>error boundary</div>
236
- <button
237
- onClick={() => {
238
- resetErrorBoundary()
239
- }}
240
- >
241
- retry
242
- </button>
243
- </div>
244
- )}
245
- >
246
- <Page />
247
- </ErrorBoundary>
248
- )}
249
- </QueryErrorResetBoundary>,
250
- )
251
-
252
- await waitFor(() =>
253
- rendered.getByText('status: loading, fetchStatus: idle'),
254
- )
255
- fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
256
- await waitFor(() => rendered.getByText('error boundary'))
257
- })
258
-
259
- it('should not retry fetch if the reset error boundary has not been reset', async () => {
260
- const key = queryKey()
261
571
 
262
- let succeed = false
263
-
264
- function Page() {
265
- const { data } = useQuery(
266
- key,
267
- async () => {
268
- await sleep(10)
269
- if (!succeed) {
270
- throw new Error('Error')
271
- } else {
272
- return 'data'
273
- }
274
- },
275
- {
276
- retry: false,
277
- useErrorBoundary: true,
278
- },
572
+ expect(rendered.queryByText('page')).not.toBeNull()
573
+ })
574
+
575
+ it('should show error boundary when using tracked queries even though we do not track the error field', async () => {
576
+ const key = queryKey()
577
+
578
+ let succeed = false
579
+
580
+ function Page() {
581
+ const { data } = useQuery(
582
+ key,
583
+ async () => {
584
+ await sleep(10)
585
+ if (!succeed) {
586
+ throw new Error('Error')
587
+ } else {
588
+ return 'data'
589
+ }
590
+ },
591
+ {
592
+ retry: false,
593
+ useErrorBoundary: true,
594
+ },
595
+ )
596
+ return <div>{data}</div>
597
+ }
598
+
599
+ const rendered = renderWithClient(
600
+ queryClient,
601
+ <QueryErrorResetBoundary>
602
+ {({ reset }) => (
603
+ <ErrorBoundary
604
+ onReset={reset}
605
+ fallbackRender={({ resetErrorBoundary }) => (
606
+ <div>
607
+ <div>error boundary</div>
608
+ <button
609
+ onClick={() => {
610
+ resetErrorBoundary()
611
+ }}
612
+ >
613
+ retry
614
+ </button>
615
+ </div>
616
+ )}
617
+ >
618
+ <Page />
619
+ </ErrorBoundary>
620
+ )}
621
+ </QueryErrorResetBoundary>,
279
622
  )
280
- return <div>{data}</div>
281
- }
282
-
283
- const rendered = renderWithClient(
284
- queryClient,
285
- <QueryErrorResetBoundary>
286
- {() => (
287
- <ErrorBoundary
288
- fallbackRender={({ resetErrorBoundary }) => (
289
- <div>
290
- <div>error boundary</div>
291
- <button
292
- onClick={() => {
293
- resetErrorBoundary()
294
- }}
295
- >
296
- retry
297
- </button>
298
- </div>
299
- )}
300
- >
301
- <Page />
302
- </ErrorBoundary>
303
- )}
304
- </QueryErrorResetBoundary>,
305
- )
306
-
307
- await waitFor(() => rendered.getByText('error boundary'))
308
- await waitFor(() => rendered.getByText('retry'))
309
- succeed = true
310
- fireEvent.click(rendered.getByText('retry'))
311
- await waitFor(() => rendered.getByText('error boundary'))
312
- })
313
-
314
- it('should retry fetch if the reset error boundary has been reset and the query contains data from a previous fetch', async () => {
315
- const key = queryKey()
316
623
 
317
- let succeed = false
318
-
319
- function Page() {
320
- const { data } = useQuery(
321
- key,
322
- async () => {
323
- await sleep(10)
324
- if (!succeed) {
325
- throw new Error('Error')
326
- } else {
327
- return 'data'
328
- }
329
- },
330
- {
331
- retry: false,
332
- useErrorBoundary: true,
333
- initialData: 'initial',
334
- },
335
- )
336
- return <div>{data}</div>
337
- }
338
-
339
- const rendered = renderWithClient(
340
- queryClient,
341
- <QueryErrorResetBoundary>
342
- {({ reset }) => (
343
- <ErrorBoundary
344
- onReset={reset}
345
- fallbackRender={({ resetErrorBoundary }) => (
346
- <div>
347
- <div>error boundary</div>
348
- <button
349
- onClick={() => {
350
- resetErrorBoundary()
351
- }}
352
- >
353
- retry
354
- </button>
355
- </div>
356
- )}
357
- >
358
- <Page />
359
- </ErrorBoundary>
360
- )}
361
- </QueryErrorResetBoundary>,
362
- )
363
-
364
- await waitFor(() => rendered.getByText('error boundary'))
365
- await waitFor(() => rendered.getByText('retry'))
366
- succeed = true
367
- fireEvent.click(rendered.getByText('retry'))
368
- await waitFor(() => rendered.getByText('data'))
624
+ await waitFor(() => rendered.getByText('error boundary'))
625
+ await waitFor(() => rendered.getByText('retry'))
626
+ succeed = true
627
+ fireEvent.click(rendered.getByText('retry'))
628
+ await waitFor(() => rendered.getByText('data'))
629
+ })
369
630
  })
370
631
 
371
- it('should not retry fetch if the reset error boundary has not been reset after a previous reset', async () => {
372
- const key = queryKey()
632
+ describe('useQueries', () => {
633
+ it('should retry fetch if the reset error boundary has been reset', async () => {
634
+ const key = queryKey()
373
635
 
374
- let succeed = false
375
- let shouldReset = true
636
+ let succeed = false
376
637
 
377
- function Page() {
378
- const { data } = useQuery(
379
- key,
380
- async () => {
638
+ const queryOptions = {
639
+ queryKey: key,
640
+ queryFn: async () => {
381
641
  await sleep(10)
382
642
  if (!succeed) {
383
643
  throw new Error('Error')
@@ -385,201 +645,56 @@ describe('QueryErrorResetBoundary', () => {
385
645
  return 'data'
386
646
  }
387
647
  },
388
- {
389
- retry: false,
390
- useErrorBoundary: true,
391
- },
392
- )
393
- return <div>{data}</div>
394
- }
395
-
396
- const rendered = renderWithClient(
397
- queryClient,
398
- <QueryErrorResetBoundary>
399
- {({ reset }) => (
400
- <ErrorBoundary
401
- onReset={() => {
402
- if (shouldReset) {
403
- reset()
404
- }
405
- }}
406
- fallbackRender={({ resetErrorBoundary }) => (
407
- <div>
408
- <div>error boundary</div>
409
- <button
410
- onClick={() => {
411
- resetErrorBoundary()
412
- }}
413
- >
414
- retry
415
- </button>
416
- </div>
417
- )}
418
- >
419
- <Page />
420
- </ErrorBoundary>
421
- )}
422
- </QueryErrorResetBoundary>,
423
- )
424
-
425
- await waitFor(() => rendered.getByText('error boundary'))
426
- await waitFor(() => rendered.getByText('retry'))
427
- shouldReset = true
428
- fireEvent.click(rendered.getByText('retry'))
429
- await waitFor(() => rendered.getByText('error boundary'))
430
- succeed = true
431
- shouldReset = false
432
- fireEvent.click(rendered.getByText('retry'))
433
- await waitFor(() => rendered.getByText('error boundary'))
434
- })
435
-
436
- it('should throw again on error after the reset error boundary has been reset', async () => {
437
- const key = queryKey()
438
- let fetchCount = 0
439
-
440
- function Page() {
441
- const { data } = useQuery<string>(
442
- key,
443
- async () => {
444
- fetchCount++
445
- await sleep(10)
446
- throw new Error('Error')
447
- },
448
- {
449
- retry: false,
450
- useErrorBoundary: true,
451
- },
452
- )
453
- return <div>{data}</div>
454
- }
455
-
456
- const rendered = renderWithClient(
457
- queryClient,
458
- <QueryErrorResetBoundary>
459
- {({ reset }) => (
460
- <ErrorBoundary
461
- onReset={reset}
462
- fallbackRender={({ resetErrorBoundary }) => (
463
- <div>
464
- <div>error boundary</div>
465
- <button
466
- onClick={() => {
467
- resetErrorBoundary()
468
- }}
469
- >
470
- retry
471
- </button>
472
- </div>
473
- )}
474
- >
475
- <Page />
476
- </ErrorBoundary>
477
- )}
478
- </QueryErrorResetBoundary>,
479
- )
480
-
481
- await waitFor(() => rendered.getByText('error boundary'))
482
- await waitFor(() => rendered.getByText('retry'))
483
- fireEvent.click(rendered.getByText('retry'))
484
- await waitFor(() => rendered.getByText('error boundary'))
485
- await waitFor(() => rendered.getByText('retry'))
486
- fireEvent.click(rendered.getByText('retry'))
487
- await waitFor(() => rendered.getByText('error boundary'))
488
- expect(fetchCount).toBe(3)
489
- })
490
-
491
- it('should never render the component while the query is in error state', async () => {
492
- const key = queryKey()
493
- let fetchCount = 0
494
- let renders = 0
495
-
496
- function Page() {
497
- const { data } = useQuery(
498
- key,
499
- async () => {
500
- fetchCount++
501
- await sleep(10)
502
- if (fetchCount > 2) {
503
- return 'data'
504
- } else {
505
- throw new Error('Error')
506
- }
507
- },
508
- {
509
- retry: false,
510
- suspense: true,
511
- },
512
- )
513
- renders++
514
- return <div>{data}</div>
515
- }
516
-
517
- const rendered = renderWithClient(
518
- queryClient,
519
- <QueryErrorResetBoundary>
520
- {({ reset }) => (
521
- <ErrorBoundary
522
- onReset={reset}
523
- fallbackRender={({ resetErrorBoundary }) => (
524
- <div>
525
- <div>error boundary</div>
526
- <button
527
- onClick={() => {
528
- resetErrorBoundary()
529
- }}
530
- >
531
- retry
532
- </button>
533
- </div>
534
- )}
535
- >
536
- <React.Suspense fallback={<div>loading</div>}>
648
+ retry: false,
649
+ useErrorBoundary: true,
650
+ retryOnMount: true,
651
+ }
652
+
653
+ function Page() {
654
+ const [{ data }] = useQueries({ queries: [queryOptions] })
655
+ return <div>{data}</div>
656
+ }
657
+
658
+ const rendered = renderWithClient(
659
+ queryClient,
660
+ <QueryErrorResetBoundary>
661
+ {({ reset }) => (
662
+ <ErrorBoundary
663
+ onReset={reset}
664
+ fallbackRender={({ resetErrorBoundary }) => (
665
+ <div>
666
+ <div>error boundary</div>
667
+ <button
668
+ onClick={() => {
669
+ resetErrorBoundary()
670
+ }}
671
+ >
672
+ retry
673
+ </button>
674
+ </div>
675
+ )}
676
+ >
537
677
  <Page />
538
- </React.Suspense>
539
- </ErrorBoundary>
540
- )}
541
- </QueryErrorResetBoundary>,
542
- )
543
-
544
- await waitFor(() => rendered.getByText('error boundary'))
545
- await waitFor(() => rendered.getByText('retry'))
546
- fireEvent.click(rendered.getByText('retry'))
547
- await waitFor(() => rendered.getByText('error boundary'))
548
- await waitFor(() => rendered.getByText('retry'))
549
- fireEvent.click(rendered.getByText('retry'))
550
- await waitFor(() => rendered.getByText('data'))
551
- expect(fetchCount).toBe(3)
552
- expect(renders).toBe(1)
553
- })
554
-
555
- it('should render children', async () => {
556
- function Page() {
557
- return (
558
- <div>
559
- <span>page</span>
560
- </div>
678
+ </ErrorBoundary>
679
+ )}
680
+ </QueryErrorResetBoundary>,
561
681
  )
562
- }
563
-
564
- const rendered = renderWithClient(
565
- queryClient,
566
- <QueryErrorResetBoundary>
567
- <Page />
568
- </QueryErrorResetBoundary>,
569
- )
570
682
 
571
- expect(rendered.queryByText('page')).not.toBeNull()
572
- })
683
+ await waitFor(() => rendered.getByText('error boundary'))
684
+ await waitFor(() => rendered.getByText('retry'))
685
+ succeed = true
686
+ fireEvent.click(rendered.getByText('retry'))
687
+ await waitFor(() => rendered.getByText('data'))
688
+ })
573
689
 
574
- it('should show error boundary when using tracked queries even though we do not track the error field', async () => {
575
- const key = queryKey()
690
+ it('with suspense should retry fetch if the reset error boundary has been reset', async () => {
691
+ const key = queryKey()
576
692
 
577
- let succeed = false
693
+ let succeed = false
578
694
 
579
- function Page() {
580
- const { data } = useQuery(
581
- key,
582
- async () => {
695
+ const queryOptions = {
696
+ queryKey: key,
697
+ queryFn: async () => {
583
698
  await sleep(10)
584
699
  if (!succeed) {
585
700
  throw new Error('Error')
@@ -587,43 +702,49 @@ describe('QueryErrorResetBoundary', () => {
587
702
  return 'data'
588
703
  }
589
704
  },
590
- {
591
- retry: false,
592
- useErrorBoundary: true,
593
- },
705
+ retry: false,
706
+ useErrorBoundary: true,
707
+ retryOnMount: true,
708
+ suspense: true,
709
+ }
710
+
711
+ function Page() {
712
+ const [{ data }] = useQueries({ queries: [queryOptions] })
713
+ return <div>{data}</div>
714
+ }
715
+
716
+ const rendered = renderWithClient(
717
+ queryClient,
718
+ <QueryErrorResetBoundary>
719
+ {({ reset }) => (
720
+ <ErrorBoundary
721
+ onReset={reset}
722
+ fallbackRender={({ resetErrorBoundary }) => (
723
+ <div>
724
+ <div>error boundary</div>
725
+ <button
726
+ onClick={() => {
727
+ resetErrorBoundary()
728
+ }}
729
+ >
730
+ retry
731
+ </button>
732
+ </div>
733
+ )}
734
+ >
735
+ <React.Suspense fallback="Loader">
736
+ <Page />
737
+ </React.Suspense>
738
+ </ErrorBoundary>
739
+ )}
740
+ </QueryErrorResetBoundary>,
594
741
  )
595
- return <div>{data}</div>
596
- }
597
-
598
- const rendered = renderWithClient(
599
- queryClient,
600
- <QueryErrorResetBoundary>
601
- {({ reset }) => (
602
- <ErrorBoundary
603
- onReset={reset}
604
- fallbackRender={({ resetErrorBoundary }) => (
605
- <div>
606
- <div>error boundary</div>
607
- <button
608
- onClick={() => {
609
- resetErrorBoundary()
610
- }}
611
- >
612
- retry
613
- </button>
614
- </div>
615
- )}
616
- >
617
- <Page />
618
- </ErrorBoundary>
619
- )}
620
- </QueryErrorResetBoundary>,
621
- )
622
-
623
- await waitFor(() => rendered.getByText('error boundary'))
624
- await waitFor(() => rendered.getByText('retry'))
625
- succeed = true
626
- fireEvent.click(rendered.getByText('retry'))
627
- await waitFor(() => rendered.getByText('data'))
742
+
743
+ await waitFor(() => rendered.getByText('error boundary'))
744
+ await waitFor(() => rendered.getByText('retry'))
745
+ succeed = true
746
+ fireEvent.click(rendered.getByText('retry'))
747
+ await waitFor(() => rendered.getByText('data'))
748
+ })
628
749
  })
629
750
  })