@tanstack/react-query 5.56.1 → 5.59.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.
- package/build/legacy/types.cjs.map +1 -1
- package/build/legacy/types.d.cts +2 -2
- package/build/legacy/types.d.ts +2 -2
- package/build/legacy/useBaseQuery.cjs +19 -3
- package/build/legacy/useBaseQuery.cjs.map +1 -1
- package/build/legacy/useBaseQuery.d.cts +1 -1
- package/build/legacy/useBaseQuery.d.ts +1 -1
- package/build/legacy/useBaseQuery.js +22 -5
- package/build/legacy/useBaseQuery.js.map +1 -1
- package/build/modern/types.cjs.map +1 -1
- package/build/modern/types.d.cts +2 -2
- package/build/modern/types.d.ts +2 -2
- package/build/modern/useBaseQuery.cjs +18 -2
- package/build/modern/useBaseQuery.cjs.map +1 -1
- package/build/modern/useBaseQuery.d.cts +1 -1
- package/build/modern/useBaseQuery.d.ts +1 -1
- package/build/modern/useBaseQuery.js +21 -4
- package/build/modern/useBaseQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/prefetch.test.tsx +10 -1
- package/src/__tests__/ssr.test.tsx +4 -2
- package/src/__tests__/suspense.test.tsx +0 -1
- package/src/__tests__/useInfiniteQuery.test.tsx +77 -1
- package/src/__tests__/useQuery.test.tsx +793 -11
- package/src/types.ts +5 -2
- package/src/useBaseQuery.ts +28 -4
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
beforeAll,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
expectTypeOf,
|
|
7
|
+
it,
|
|
8
|
+
test,
|
|
9
|
+
vi,
|
|
10
|
+
} from 'vitest'
|
|
2
11
|
import { act, fireEvent, render, waitFor } from '@testing-library/react'
|
|
3
12
|
import * as React from 'react'
|
|
4
13
|
import { ErrorBoundary } from 'react-error-boundary'
|
|
@@ -25,7 +34,9 @@ import type { Mock } from 'vitest'
|
|
|
25
34
|
|
|
26
35
|
describe('useQuery', () => {
|
|
27
36
|
const queryCache = new QueryCache()
|
|
28
|
-
const queryClient = createQueryClient({
|
|
37
|
+
const queryClient = createQueryClient({
|
|
38
|
+
queryCache,
|
|
39
|
+
})
|
|
29
40
|
|
|
30
41
|
it('should return the correct types', () => {
|
|
31
42
|
const key = queryKey()
|
|
@@ -41,6 +52,7 @@ describe('useQuery', () => {
|
|
|
41
52
|
const fromQueryFn = useQuery({ queryKey: key, queryFn: () => 'test' })
|
|
42
53
|
expectTypeOf(fromQueryFn.data).toEqualTypeOf<string | undefined>()
|
|
43
54
|
expectTypeOf(fromQueryFn.error).toEqualTypeOf<Error | null>()
|
|
55
|
+
expectTypeOf(fromQueryFn.promise).toEqualTypeOf<Promise<string>>()
|
|
44
56
|
|
|
45
57
|
// it should be possible to specify the result type
|
|
46
58
|
const withResult = useQuery<string>({
|
|
@@ -270,6 +282,7 @@ describe('useQuery', () => {
|
|
|
270
282
|
refetch: expect.any(Function),
|
|
271
283
|
status: 'pending',
|
|
272
284
|
fetchStatus: 'fetching',
|
|
285
|
+
promise: expect.any(Promise),
|
|
273
286
|
})
|
|
274
287
|
|
|
275
288
|
expect(states[1]).toEqual({
|
|
@@ -297,18 +310,22 @@ describe('useQuery', () => {
|
|
|
297
310
|
refetch: expect.any(Function),
|
|
298
311
|
status: 'success',
|
|
299
312
|
fetchStatus: 'idle',
|
|
313
|
+
promise: expect.any(Promise),
|
|
300
314
|
})
|
|
315
|
+
|
|
316
|
+
expect(states[0]!.promise).toEqual(states[1]!.promise)
|
|
301
317
|
})
|
|
302
318
|
|
|
303
319
|
it('should return the correct states for an unsuccessful query', async () => {
|
|
304
320
|
const key = queryKey()
|
|
305
321
|
|
|
306
322
|
const states: Array<UseQueryResult> = []
|
|
323
|
+
let index = 0
|
|
307
324
|
|
|
308
325
|
function Page() {
|
|
309
326
|
const state = useQuery({
|
|
310
327
|
queryKey: key,
|
|
311
|
-
queryFn: () => Promise.reject(new Error(
|
|
328
|
+
queryFn: () => Promise.reject(new Error(`rejected #${++index}`)),
|
|
312
329
|
|
|
313
330
|
retry: 1,
|
|
314
331
|
retryDelay: 1,
|
|
@@ -354,6 +371,7 @@ describe('useQuery', () => {
|
|
|
354
371
|
refetch: expect.any(Function),
|
|
355
372
|
status: 'pending',
|
|
356
373
|
fetchStatus: 'fetching',
|
|
374
|
+
promise: expect.any(Promise),
|
|
357
375
|
})
|
|
358
376
|
|
|
359
377
|
expect(states[1]).toEqual({
|
|
@@ -362,7 +380,7 @@ describe('useQuery', () => {
|
|
|
362
380
|
error: null,
|
|
363
381
|
errorUpdatedAt: 0,
|
|
364
382
|
failureCount: 1,
|
|
365
|
-
failureReason: new Error('rejected'),
|
|
383
|
+
failureReason: new Error('rejected #1'),
|
|
366
384
|
errorUpdateCount: 0,
|
|
367
385
|
isError: false,
|
|
368
386
|
isFetched: false,
|
|
@@ -381,15 +399,16 @@ describe('useQuery', () => {
|
|
|
381
399
|
refetch: expect.any(Function),
|
|
382
400
|
status: 'pending',
|
|
383
401
|
fetchStatus: 'fetching',
|
|
402
|
+
promise: expect.any(Promise),
|
|
384
403
|
})
|
|
385
404
|
|
|
386
405
|
expect(states[2]).toEqual({
|
|
387
406
|
data: undefined,
|
|
388
407
|
dataUpdatedAt: 0,
|
|
389
|
-
error: new Error('rejected'),
|
|
408
|
+
error: new Error('rejected #2'),
|
|
390
409
|
errorUpdatedAt: expect.any(Number),
|
|
391
410
|
failureCount: 2,
|
|
392
|
-
failureReason: new Error('rejected'),
|
|
411
|
+
failureReason: new Error('rejected #2'),
|
|
393
412
|
errorUpdateCount: 1,
|
|
394
413
|
isError: true,
|
|
395
414
|
isFetched: true,
|
|
@@ -408,7 +427,11 @@ describe('useQuery', () => {
|
|
|
408
427
|
refetch: expect.any(Function),
|
|
409
428
|
status: 'error',
|
|
410
429
|
fetchStatus: 'idle',
|
|
430
|
+
promise: expect.any(Promise),
|
|
411
431
|
})
|
|
432
|
+
|
|
433
|
+
expect(states[0]!.promise).toEqual(states[1]!.promise)
|
|
434
|
+
expect(states[1]!.promise).toEqual(states[2]!.promise)
|
|
412
435
|
})
|
|
413
436
|
|
|
414
437
|
it('should set isFetchedAfterMount to true after a query has been fetched', async () => {
|
|
@@ -659,7 +682,7 @@ describe('useQuery', () => {
|
|
|
659
682
|
},
|
|
660
683
|
|
|
661
684
|
gcTime: 0,
|
|
662
|
-
notifyOnChangeProps: '
|
|
685
|
+
notifyOnChangeProps: ['isPending', 'isSuccess', 'data'],
|
|
663
686
|
})
|
|
664
687
|
|
|
665
688
|
states.push(state)
|
|
@@ -697,13 +720,29 @@ describe('useQuery', () => {
|
|
|
697
720
|
|
|
698
721
|
expect(states.length).toBe(4)
|
|
699
722
|
// First load
|
|
700
|
-
expect(states[0]).toMatchObject({
|
|
723
|
+
expect(states[0]).toMatchObject({
|
|
724
|
+
isPending: true,
|
|
725
|
+
isSuccess: false,
|
|
726
|
+
data: undefined,
|
|
727
|
+
})
|
|
701
728
|
// First success
|
|
702
|
-
expect(states[1]).toMatchObject({
|
|
729
|
+
expect(states[1]).toMatchObject({
|
|
730
|
+
isPending: false,
|
|
731
|
+
isSuccess: true,
|
|
732
|
+
data: 'data',
|
|
733
|
+
})
|
|
703
734
|
// Remove
|
|
704
|
-
expect(states[2]).toMatchObject({
|
|
735
|
+
expect(states[2]).toMatchObject({
|
|
736
|
+
isPending: true,
|
|
737
|
+
isSuccess: false,
|
|
738
|
+
data: undefined,
|
|
739
|
+
})
|
|
705
740
|
// Second success
|
|
706
|
-
expect(states[3]).toMatchObject({
|
|
741
|
+
expect(states[3]).toMatchObject({
|
|
742
|
+
isPending: false,
|
|
743
|
+
isSuccess: true,
|
|
744
|
+
data: 'data',
|
|
745
|
+
})
|
|
707
746
|
})
|
|
708
747
|
|
|
709
748
|
it('should fetch when refetchOnMount is false and nothing has been fetched yet', async () => {
|
|
@@ -6581,4 +6620,747 @@ describe('useQuery', () => {
|
|
|
6581
6620
|
|
|
6582
6621
|
consoleMock.mockRestore()
|
|
6583
6622
|
})
|
|
6623
|
+
|
|
6624
|
+
describe('useQuery().promise', () => {
|
|
6625
|
+
beforeAll(() => {
|
|
6626
|
+
queryClient.setDefaultOptions({
|
|
6627
|
+
queries: { experimental_prefetchInRender: true },
|
|
6628
|
+
})
|
|
6629
|
+
})
|
|
6630
|
+
afterAll(() => {
|
|
6631
|
+
queryClient.setDefaultOptions({
|
|
6632
|
+
queries: { experimental_prefetchInRender: false },
|
|
6633
|
+
})
|
|
6634
|
+
})
|
|
6635
|
+
it('should work with a basic test', async () => {
|
|
6636
|
+
const key = queryKey()
|
|
6637
|
+
let suspenseRenderCount = 0
|
|
6638
|
+
let pageRenderCount = 0
|
|
6639
|
+
|
|
6640
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6641
|
+
const data = React.use(props.promise)
|
|
6642
|
+
|
|
6643
|
+
return <>{data}</>
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
function Loading() {
|
|
6647
|
+
suspenseRenderCount++
|
|
6648
|
+
return <>loading..</>
|
|
6649
|
+
}
|
|
6650
|
+
function Page() {
|
|
6651
|
+
const query = useQuery({
|
|
6652
|
+
queryKey: key,
|
|
6653
|
+
queryFn: async () => {
|
|
6654
|
+
await sleep(1)
|
|
6655
|
+
return 'test'
|
|
6656
|
+
},
|
|
6657
|
+
})
|
|
6658
|
+
|
|
6659
|
+
pageRenderCount++
|
|
6660
|
+
return (
|
|
6661
|
+
<React.Suspense fallback={<Loading />}>
|
|
6662
|
+
<MyComponent promise={query.promise} />
|
|
6663
|
+
</React.Suspense>
|
|
6664
|
+
)
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6667
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6668
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
6669
|
+
await waitFor(() => rendered.getByText('test'))
|
|
6670
|
+
|
|
6671
|
+
// Suspense should rendered once since `.promise` is the only watched property
|
|
6672
|
+
expect(suspenseRenderCount).toBe(1)
|
|
6673
|
+
|
|
6674
|
+
// Page should be rendered once since since the promise do not change
|
|
6675
|
+
expect(pageRenderCount).toBe(1)
|
|
6676
|
+
})
|
|
6677
|
+
|
|
6678
|
+
it('colocate suspense and promise', async () => {
|
|
6679
|
+
const key = queryKey()
|
|
6680
|
+
let suspenseRenderCount = 0
|
|
6681
|
+
let pageRenderCount = 0
|
|
6682
|
+
let callCount = 0
|
|
6683
|
+
|
|
6684
|
+
function MyComponent() {
|
|
6685
|
+
const query = useQuery({
|
|
6686
|
+
queryKey: key,
|
|
6687
|
+
queryFn: async () => {
|
|
6688
|
+
callCount++
|
|
6689
|
+
await sleep(1)
|
|
6690
|
+
return 'test'
|
|
6691
|
+
},
|
|
6692
|
+
staleTime: 1000,
|
|
6693
|
+
})
|
|
6694
|
+
const data = React.use(query.promise)
|
|
6695
|
+
|
|
6696
|
+
return <>{data}</>
|
|
6697
|
+
}
|
|
6698
|
+
|
|
6699
|
+
function Loading() {
|
|
6700
|
+
suspenseRenderCount++
|
|
6701
|
+
return <>loading..</>
|
|
6702
|
+
}
|
|
6703
|
+
function Page() {
|
|
6704
|
+
pageRenderCount++
|
|
6705
|
+
return (
|
|
6706
|
+
<React.Suspense fallback={<Loading />}>
|
|
6707
|
+
<MyComponent />
|
|
6708
|
+
</React.Suspense>
|
|
6709
|
+
)
|
|
6710
|
+
}
|
|
6711
|
+
|
|
6712
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6713
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
6714
|
+
await waitFor(() => rendered.getByText('test'))
|
|
6715
|
+
|
|
6716
|
+
// Suspense should rendered once since `.promise` is the only watched property
|
|
6717
|
+
expect(suspenseRenderCount).toBe(1)
|
|
6718
|
+
|
|
6719
|
+
// Page should be rendered once since since the promise do not change
|
|
6720
|
+
expect(pageRenderCount).toBe(1)
|
|
6721
|
+
|
|
6722
|
+
expect(callCount).toBe(1)
|
|
6723
|
+
})
|
|
6724
|
+
|
|
6725
|
+
it('parallel queries', async () => {
|
|
6726
|
+
const key = queryKey()
|
|
6727
|
+
let suspenseRenderCount = 0
|
|
6728
|
+
let pageRenderCount = 0
|
|
6729
|
+
let callCount = 0
|
|
6730
|
+
|
|
6731
|
+
function MyComponent() {
|
|
6732
|
+
const query = useQuery({
|
|
6733
|
+
queryKey: key,
|
|
6734
|
+
queryFn: async () => {
|
|
6735
|
+
callCount++
|
|
6736
|
+
await sleep(1)
|
|
6737
|
+
return 'test'
|
|
6738
|
+
},
|
|
6739
|
+
staleTime: 1000,
|
|
6740
|
+
})
|
|
6741
|
+
const data = React.use(query.promise)
|
|
6742
|
+
|
|
6743
|
+
return data
|
|
6744
|
+
}
|
|
6745
|
+
|
|
6746
|
+
function Loading() {
|
|
6747
|
+
suspenseRenderCount++
|
|
6748
|
+
return <>loading..</>
|
|
6749
|
+
}
|
|
6750
|
+
function Page() {
|
|
6751
|
+
pageRenderCount++
|
|
6752
|
+
return (
|
|
6753
|
+
<>
|
|
6754
|
+
<React.Suspense fallback={<Loading />}>
|
|
6755
|
+
<MyComponent />
|
|
6756
|
+
<MyComponent />
|
|
6757
|
+
<MyComponent />
|
|
6758
|
+
</React.Suspense>
|
|
6759
|
+
<React.Suspense fallback={null}>
|
|
6760
|
+
<MyComponent />
|
|
6761
|
+
<MyComponent />
|
|
6762
|
+
</React.Suspense>
|
|
6763
|
+
</>
|
|
6764
|
+
)
|
|
6765
|
+
}
|
|
6766
|
+
|
|
6767
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6768
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
6769
|
+
await waitFor(() => {
|
|
6770
|
+
expect(rendered.queryByText('loading..')).not.toBeInTheDocument()
|
|
6771
|
+
})
|
|
6772
|
+
|
|
6773
|
+
expect(rendered.container.textContent).toBe('test'.repeat(5))
|
|
6774
|
+
|
|
6775
|
+
// Suspense should rendered once since `.promise` is the only watched property
|
|
6776
|
+
expect(suspenseRenderCount).toBe(1)
|
|
6777
|
+
|
|
6778
|
+
// Page should be rendered once since since the promise do not change
|
|
6779
|
+
expect(pageRenderCount).toBe(1)
|
|
6780
|
+
|
|
6781
|
+
expect(callCount).toBe(1)
|
|
6782
|
+
})
|
|
6783
|
+
|
|
6784
|
+
it('should work with initial data', async () => {
|
|
6785
|
+
const key = queryKey()
|
|
6786
|
+
let suspenseRenderCount = 0
|
|
6787
|
+
let pageRenderCount = 0
|
|
6788
|
+
|
|
6789
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6790
|
+
const data = React.use(props.promise)
|
|
6791
|
+
|
|
6792
|
+
return <>{data}</>
|
|
6793
|
+
}
|
|
6794
|
+
function Loading() {
|
|
6795
|
+
suspenseRenderCount++
|
|
6796
|
+
|
|
6797
|
+
return <>loading..</>
|
|
6798
|
+
}
|
|
6799
|
+
function Page() {
|
|
6800
|
+
const query = useQuery({
|
|
6801
|
+
queryKey: key,
|
|
6802
|
+
queryFn: async () => {
|
|
6803
|
+
await sleep(1)
|
|
6804
|
+
return 'test'
|
|
6805
|
+
},
|
|
6806
|
+
initialData: 'initial',
|
|
6807
|
+
})
|
|
6808
|
+
pageRenderCount++
|
|
6809
|
+
|
|
6810
|
+
return (
|
|
6811
|
+
<React.Suspense fallback={<Loading />}>
|
|
6812
|
+
<MyComponent promise={query.promise} />
|
|
6813
|
+
</React.Suspense>
|
|
6814
|
+
)
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6817
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6818
|
+
await waitFor(() => rendered.getByText('initial'))
|
|
6819
|
+
await waitFor(() => rendered.getByText('test'))
|
|
6820
|
+
|
|
6821
|
+
// Suspense boundary should never be rendered since it has data immediately
|
|
6822
|
+
expect(suspenseRenderCount).toBe(0)
|
|
6823
|
+
// Page should only be rendered twice since, the promise will get swapped out when new result comes in
|
|
6824
|
+
expect(pageRenderCount).toBe(2)
|
|
6825
|
+
})
|
|
6826
|
+
|
|
6827
|
+
it('should not fetch with initial data and staleTime', async () => {
|
|
6828
|
+
const key = queryKey()
|
|
6829
|
+
let suspenseRenderCount = 0
|
|
6830
|
+
const queryFn = vi.fn().mockImplementation(async () => {
|
|
6831
|
+
await sleep(1)
|
|
6832
|
+
return 'test'
|
|
6833
|
+
})
|
|
6834
|
+
|
|
6835
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6836
|
+
const data = React.use(props.promise)
|
|
6837
|
+
|
|
6838
|
+
return <>{data}</>
|
|
6839
|
+
}
|
|
6840
|
+
function Loading() {
|
|
6841
|
+
suspenseRenderCount++
|
|
6842
|
+
|
|
6843
|
+
return <>loading..</>
|
|
6844
|
+
}
|
|
6845
|
+
function Page() {
|
|
6846
|
+
const query = useQuery({
|
|
6847
|
+
queryKey: key,
|
|
6848
|
+
queryFn,
|
|
6849
|
+
initialData: 'initial',
|
|
6850
|
+
staleTime: 1000,
|
|
6851
|
+
})
|
|
6852
|
+
|
|
6853
|
+
return (
|
|
6854
|
+
<React.Suspense fallback={<Loading />}>
|
|
6855
|
+
<MyComponent promise={query.promise} />
|
|
6856
|
+
</React.Suspense>
|
|
6857
|
+
)
|
|
6858
|
+
}
|
|
6859
|
+
|
|
6860
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6861
|
+
await waitFor(() => rendered.getByText('initial'))
|
|
6862
|
+
|
|
6863
|
+
// Suspense boundary should never be rendered since it has data immediately
|
|
6864
|
+
expect(suspenseRenderCount).toBe(0)
|
|
6865
|
+
// should not call queryFn because of staleTime + initialData combo
|
|
6866
|
+
expect(queryFn).toHaveBeenCalledTimes(0)
|
|
6867
|
+
})
|
|
6868
|
+
|
|
6869
|
+
it('should work with static placeholderData', async () => {
|
|
6870
|
+
const key = queryKey()
|
|
6871
|
+
let suspenseRenderCount = 0
|
|
6872
|
+
let pageRenderCount = 0
|
|
6873
|
+
|
|
6874
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6875
|
+
const data = React.use(props.promise)
|
|
6876
|
+
|
|
6877
|
+
return <>{data}</>
|
|
6878
|
+
}
|
|
6879
|
+
function Loading() {
|
|
6880
|
+
suspenseRenderCount++
|
|
6881
|
+
|
|
6882
|
+
return <>loading..</>
|
|
6883
|
+
}
|
|
6884
|
+
function Page() {
|
|
6885
|
+
const query = useQuery({
|
|
6886
|
+
queryKey: key,
|
|
6887
|
+
queryFn: async () => {
|
|
6888
|
+
await sleep(1)
|
|
6889
|
+
return 'test'
|
|
6890
|
+
},
|
|
6891
|
+
placeholderData: 'placeholder',
|
|
6892
|
+
})
|
|
6893
|
+
pageRenderCount++
|
|
6894
|
+
|
|
6895
|
+
return (
|
|
6896
|
+
<React.Suspense fallback={<Loading />}>
|
|
6897
|
+
<MyComponent promise={query.promise} />
|
|
6898
|
+
</React.Suspense>
|
|
6899
|
+
)
|
|
6900
|
+
}
|
|
6901
|
+
|
|
6902
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6903
|
+
await waitFor(() => rendered.getByText('placeholder'))
|
|
6904
|
+
await waitFor(() => rendered.getByText('test'))
|
|
6905
|
+
|
|
6906
|
+
// Suspense boundary should never be rendered since it has data immediately
|
|
6907
|
+
expect(suspenseRenderCount).toBe(0)
|
|
6908
|
+
// Page should only be rendered twice since, the promise will get swapped out when new result comes in
|
|
6909
|
+
expect(pageRenderCount).toBe(2)
|
|
6910
|
+
})
|
|
6911
|
+
|
|
6912
|
+
it('should work with placeholderData: keepPreviousData', async () => {
|
|
6913
|
+
const key = queryKey()
|
|
6914
|
+
let suspenseRenderCount = 0
|
|
6915
|
+
|
|
6916
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6917
|
+
const data = React.use(props.promise)
|
|
6918
|
+
|
|
6919
|
+
return <>{data}</>
|
|
6920
|
+
}
|
|
6921
|
+
function Loading() {
|
|
6922
|
+
suspenseRenderCount++
|
|
6923
|
+
|
|
6924
|
+
return <>loading..</>
|
|
6925
|
+
}
|
|
6926
|
+
function Page() {
|
|
6927
|
+
const [count, setCount] = React.useState(0)
|
|
6928
|
+
const query = useQuery({
|
|
6929
|
+
queryKey: [...key, count],
|
|
6930
|
+
queryFn: async () => {
|
|
6931
|
+
await sleep(1)
|
|
6932
|
+
return 'test-' + count
|
|
6933
|
+
},
|
|
6934
|
+
placeholderData: keepPreviousData,
|
|
6935
|
+
})
|
|
6936
|
+
|
|
6937
|
+
return (
|
|
6938
|
+
<div>
|
|
6939
|
+
<React.Suspense fallback={<Loading />}>
|
|
6940
|
+
<MyComponent promise={query.promise} />
|
|
6941
|
+
</React.Suspense>
|
|
6942
|
+
<button onClick={() => setCount((c) => c + 1)}>increment</button>
|
|
6943
|
+
</div>
|
|
6944
|
+
)
|
|
6945
|
+
}
|
|
6946
|
+
|
|
6947
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6948
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
6949
|
+
await waitFor(() => rendered.getByText('test-0'))
|
|
6950
|
+
|
|
6951
|
+
// Suspense boundary should only be rendered initially
|
|
6952
|
+
expect(suspenseRenderCount).toBe(1)
|
|
6953
|
+
|
|
6954
|
+
fireEvent.click(rendered.getByRole('button', { name: 'increment' }))
|
|
6955
|
+
|
|
6956
|
+
await waitFor(() => rendered.getByText('test-1'))
|
|
6957
|
+
|
|
6958
|
+
// no more suspense boundary rendering
|
|
6959
|
+
expect(suspenseRenderCount).toBe(1)
|
|
6960
|
+
})
|
|
6961
|
+
|
|
6962
|
+
it('should be possible to select a part of the data with select', async () => {
|
|
6963
|
+
const key = queryKey()
|
|
6964
|
+
let suspenseRenderCount = 0
|
|
6965
|
+
let pageRenderCount = 0
|
|
6966
|
+
|
|
6967
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
6968
|
+
const data = React.use(props.promise)
|
|
6969
|
+
return <>{data}</>
|
|
6970
|
+
}
|
|
6971
|
+
|
|
6972
|
+
function Loading() {
|
|
6973
|
+
suspenseRenderCount++
|
|
6974
|
+
return <>loading..</>
|
|
6975
|
+
}
|
|
6976
|
+
|
|
6977
|
+
function Page() {
|
|
6978
|
+
const query = useQuery({
|
|
6979
|
+
queryKey: key,
|
|
6980
|
+
queryFn: async () => {
|
|
6981
|
+
await sleep(1)
|
|
6982
|
+
return { name: 'test' }
|
|
6983
|
+
},
|
|
6984
|
+
select: (data) => data.name,
|
|
6985
|
+
})
|
|
6986
|
+
|
|
6987
|
+
pageRenderCount++
|
|
6988
|
+
return (
|
|
6989
|
+
<React.Suspense fallback={<Loading />}>
|
|
6990
|
+
<MyComponent promise={query.promise} />
|
|
6991
|
+
</React.Suspense>
|
|
6992
|
+
)
|
|
6993
|
+
}
|
|
6994
|
+
|
|
6995
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
6996
|
+
|
|
6997
|
+
await waitFor(() => {
|
|
6998
|
+
rendered.getByText('test')
|
|
6999
|
+
})
|
|
7000
|
+
expect(suspenseRenderCount).toBe(1)
|
|
7001
|
+
expect(pageRenderCount).toBe(1)
|
|
7002
|
+
})
|
|
7003
|
+
|
|
7004
|
+
it('should throw error if the promise fails', async () => {
|
|
7005
|
+
let suspenseRenderCount = 0
|
|
7006
|
+
const consoleMock = vi
|
|
7007
|
+
.spyOn(console, 'error')
|
|
7008
|
+
.mockImplementation(() => undefined)
|
|
7009
|
+
|
|
7010
|
+
const key = queryKey()
|
|
7011
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7012
|
+
const data = React.use(props.promise)
|
|
7013
|
+
|
|
7014
|
+
return <>{data}</>
|
|
7015
|
+
}
|
|
7016
|
+
|
|
7017
|
+
function Loading() {
|
|
7018
|
+
suspenseRenderCount++
|
|
7019
|
+
return <>loading..</>
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
let queryCount = 0
|
|
7023
|
+
function Page() {
|
|
7024
|
+
const query = useQuery({
|
|
7025
|
+
queryKey: key,
|
|
7026
|
+
queryFn: async () => {
|
|
7027
|
+
await sleep(1)
|
|
7028
|
+
if (++queryCount > 1) {
|
|
7029
|
+
// second time this query mounts, it should not throw
|
|
7030
|
+
return 'data'
|
|
7031
|
+
}
|
|
7032
|
+
throw new Error('Error test')
|
|
7033
|
+
},
|
|
7034
|
+
retry: false,
|
|
7035
|
+
})
|
|
7036
|
+
|
|
7037
|
+
return (
|
|
7038
|
+
<React.Suspense fallback={<Loading />}>
|
|
7039
|
+
<MyComponent promise={query.promise} />
|
|
7040
|
+
</React.Suspense>
|
|
7041
|
+
)
|
|
7042
|
+
}
|
|
7043
|
+
|
|
7044
|
+
const rendered = renderWithClient(
|
|
7045
|
+
queryClient,
|
|
7046
|
+
<ErrorBoundary
|
|
7047
|
+
fallbackRender={(props) => (
|
|
7048
|
+
<>
|
|
7049
|
+
error boundary{' '}
|
|
7050
|
+
<button
|
|
7051
|
+
onClick={() => {
|
|
7052
|
+
props.resetErrorBoundary()
|
|
7053
|
+
}}
|
|
7054
|
+
>
|
|
7055
|
+
resetErrorBoundary
|
|
7056
|
+
</button>
|
|
7057
|
+
</>
|
|
7058
|
+
)}
|
|
7059
|
+
>
|
|
7060
|
+
<Page />
|
|
7061
|
+
</ErrorBoundary>,
|
|
7062
|
+
)
|
|
7063
|
+
|
|
7064
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7065
|
+
await waitFor(() => rendered.getByText('error boundary'))
|
|
7066
|
+
|
|
7067
|
+
consoleMock.mockRestore()
|
|
7068
|
+
|
|
7069
|
+
fireEvent.click(rendered.getByText('resetErrorBoundary'))
|
|
7070
|
+
|
|
7071
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7072
|
+
await waitFor(() => rendered.getByText('data'))
|
|
7073
|
+
|
|
7074
|
+
expect(queryCount).toBe(2)
|
|
7075
|
+
})
|
|
7076
|
+
|
|
7077
|
+
it('should recreate promise with data changes', async () => {
|
|
7078
|
+
const key = queryKey()
|
|
7079
|
+
let suspenseRenderCount = 0
|
|
7080
|
+
let pageRenderCount = 0
|
|
7081
|
+
|
|
7082
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7083
|
+
const data = React.use(props.promise)
|
|
7084
|
+
|
|
7085
|
+
return <>{data}</>
|
|
7086
|
+
}
|
|
7087
|
+
|
|
7088
|
+
function Loading() {
|
|
7089
|
+
suspenseRenderCount++
|
|
7090
|
+
return <>loading..</>
|
|
7091
|
+
}
|
|
7092
|
+
function Page() {
|
|
7093
|
+
const query = useQuery({
|
|
7094
|
+
queryKey: key,
|
|
7095
|
+
queryFn: async () => {
|
|
7096
|
+
await sleep(1)
|
|
7097
|
+
return 'test1'
|
|
7098
|
+
},
|
|
7099
|
+
})
|
|
7100
|
+
|
|
7101
|
+
pageRenderCount++
|
|
7102
|
+
return (
|
|
7103
|
+
<React.Suspense fallback={<Loading />}>
|
|
7104
|
+
<MyComponent promise={query.promise} />
|
|
7105
|
+
</React.Suspense>
|
|
7106
|
+
)
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
7110
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7111
|
+
await waitFor(() => rendered.getByText('test1'))
|
|
7112
|
+
|
|
7113
|
+
// Suspense should rendered once since `.promise` is the only watched property
|
|
7114
|
+
expect(pageRenderCount).toBe(1)
|
|
7115
|
+
|
|
7116
|
+
queryClient.setQueryData(key, 'test2')
|
|
7117
|
+
|
|
7118
|
+
await waitFor(() => rendered.getByText('test2'))
|
|
7119
|
+
|
|
7120
|
+
// Suspense should rendered once since `.promise` is the only watched property
|
|
7121
|
+
expect(suspenseRenderCount).toBe(1)
|
|
7122
|
+
|
|
7123
|
+
// Page should be rendered once since since the promise changed once
|
|
7124
|
+
expect(pageRenderCount).toBe(2)
|
|
7125
|
+
})
|
|
7126
|
+
|
|
7127
|
+
it('should dedupe when re-fetched with queryClient.fetchQuery while suspending', async () => {
|
|
7128
|
+
const key = queryKey()
|
|
7129
|
+
const queryFn = vi.fn().mockImplementation(async () => {
|
|
7130
|
+
await sleep(10)
|
|
7131
|
+
return 'test'
|
|
7132
|
+
})
|
|
7133
|
+
|
|
7134
|
+
const options = {
|
|
7135
|
+
queryKey: key,
|
|
7136
|
+
queryFn,
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7140
|
+
const data = React.use(props.promise)
|
|
7141
|
+
|
|
7142
|
+
return <>{data}</>
|
|
7143
|
+
}
|
|
7144
|
+
|
|
7145
|
+
function Loading() {
|
|
7146
|
+
return <>loading..</>
|
|
7147
|
+
}
|
|
7148
|
+
function Page() {
|
|
7149
|
+
const query = useQuery(options)
|
|
7150
|
+
|
|
7151
|
+
return (
|
|
7152
|
+
<div>
|
|
7153
|
+
<React.Suspense fallback={<Loading />}>
|
|
7154
|
+
<MyComponent promise={query.promise} />
|
|
7155
|
+
</React.Suspense>
|
|
7156
|
+
<button onClick={() => queryClient.fetchQuery(options)}>
|
|
7157
|
+
fetch
|
|
7158
|
+
</button>
|
|
7159
|
+
</div>
|
|
7160
|
+
)
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
7164
|
+
fireEvent.click(rendered.getByText('fetch'))
|
|
7165
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7166
|
+
await waitFor(() => rendered.getByText('test'))
|
|
7167
|
+
|
|
7168
|
+
expect(queryFn).toHaveBeenCalledOnce()
|
|
7169
|
+
})
|
|
7170
|
+
|
|
7171
|
+
it('should dedupe when re-fetched with refetchQueries while suspending', async () => {
|
|
7172
|
+
const key = queryKey()
|
|
7173
|
+
let count = 0
|
|
7174
|
+
const queryFn = vi.fn().mockImplementation(async () => {
|
|
7175
|
+
await sleep(10)
|
|
7176
|
+
return 'test' + count++
|
|
7177
|
+
})
|
|
7178
|
+
|
|
7179
|
+
const options = {
|
|
7180
|
+
queryKey: key,
|
|
7181
|
+
queryFn,
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7184
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7185
|
+
const data = React.use(props.promise)
|
|
7186
|
+
|
|
7187
|
+
return <>{data}</>
|
|
7188
|
+
}
|
|
7189
|
+
|
|
7190
|
+
function Loading() {
|
|
7191
|
+
return <>loading..</>
|
|
7192
|
+
}
|
|
7193
|
+
function Page() {
|
|
7194
|
+
const query = useQuery(options)
|
|
7195
|
+
|
|
7196
|
+
return (
|
|
7197
|
+
<div>
|
|
7198
|
+
<React.Suspense fallback={<Loading />}>
|
|
7199
|
+
<MyComponent promise={query.promise} />
|
|
7200
|
+
</React.Suspense>
|
|
7201
|
+
<button onClick={() => queryClient.refetchQueries(options)}>
|
|
7202
|
+
refetch
|
|
7203
|
+
</button>
|
|
7204
|
+
</div>
|
|
7205
|
+
)
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
7209
|
+
fireEvent.click(rendered.getByText('refetch'))
|
|
7210
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7211
|
+
await waitFor(() => rendered.getByText('test0'))
|
|
7212
|
+
|
|
7213
|
+
expect(queryFn).toHaveBeenCalledOnce()
|
|
7214
|
+
})
|
|
7215
|
+
|
|
7216
|
+
it('should stay pending when canceled with cancelQueries while suspending until refetched', async () => {
|
|
7217
|
+
const key = queryKey()
|
|
7218
|
+
let count = 0
|
|
7219
|
+
const queryFn = vi.fn().mockImplementation(async () => {
|
|
7220
|
+
await sleep(10)
|
|
7221
|
+
return 'test' + count++
|
|
7222
|
+
})
|
|
7223
|
+
|
|
7224
|
+
const options = {
|
|
7225
|
+
queryKey: key,
|
|
7226
|
+
queryFn,
|
|
7227
|
+
}
|
|
7228
|
+
|
|
7229
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7230
|
+
const data = React.use(props.promise)
|
|
7231
|
+
|
|
7232
|
+
return <>{data}</>
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
function Loading() {
|
|
7236
|
+
return <>loading..</>
|
|
7237
|
+
}
|
|
7238
|
+
function Page() {
|
|
7239
|
+
const query = useQuery(options)
|
|
7240
|
+
|
|
7241
|
+
return (
|
|
7242
|
+
<div>
|
|
7243
|
+
<React.Suspense fallback={<Loading />}>
|
|
7244
|
+
<MyComponent promise={query.promise} />
|
|
7245
|
+
</React.Suspense>
|
|
7246
|
+
<button onClick={() => queryClient.cancelQueries(options)}>
|
|
7247
|
+
cancel
|
|
7248
|
+
</button>
|
|
7249
|
+
<button
|
|
7250
|
+
onClick={() => queryClient.setQueryData<string>(key, 'hello')}
|
|
7251
|
+
>
|
|
7252
|
+
fetch
|
|
7253
|
+
</button>
|
|
7254
|
+
</div>
|
|
7255
|
+
)
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
const rendered = renderWithClient(
|
|
7259
|
+
queryClient,
|
|
7260
|
+
<ErrorBoundary fallbackRender={() => <>error boundary</>}>
|
|
7261
|
+
<Page />
|
|
7262
|
+
</ErrorBoundary>,
|
|
7263
|
+
)
|
|
7264
|
+
fireEvent.click(rendered.getByText('cancel'))
|
|
7265
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7266
|
+
// await waitFor(() => rendered.getByText('error boundary'))
|
|
7267
|
+
await waitFor(() =>
|
|
7268
|
+
expect(queryClient.getQueryState(key)).toMatchObject({
|
|
7269
|
+
status: 'pending',
|
|
7270
|
+
fetchStatus: 'idle',
|
|
7271
|
+
}),
|
|
7272
|
+
)
|
|
7273
|
+
|
|
7274
|
+
expect(queryFn).toHaveBeenCalledOnce()
|
|
7275
|
+
|
|
7276
|
+
fireEvent.click(rendered.getByText('fetch'))
|
|
7277
|
+
|
|
7278
|
+
await waitFor(() => rendered.getByText('hello'))
|
|
7279
|
+
})
|
|
7280
|
+
|
|
7281
|
+
it('should resolve to previous data when canceled with cancelQueries while suspending', async () => {
|
|
7282
|
+
const key = queryKey()
|
|
7283
|
+
const queryFn = vi.fn().mockImplementation(async () => {
|
|
7284
|
+
await sleep(10)
|
|
7285
|
+
return 'test'
|
|
7286
|
+
})
|
|
7287
|
+
|
|
7288
|
+
const options = {
|
|
7289
|
+
queryKey: key,
|
|
7290
|
+
queryFn,
|
|
7291
|
+
}
|
|
7292
|
+
|
|
7293
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7294
|
+
const data = React.use(props.promise)
|
|
7295
|
+
|
|
7296
|
+
return <>{data}</>
|
|
7297
|
+
}
|
|
7298
|
+
|
|
7299
|
+
function Loading() {
|
|
7300
|
+
return <>loading..</>
|
|
7301
|
+
}
|
|
7302
|
+
function Page() {
|
|
7303
|
+
const query = useQuery(options)
|
|
7304
|
+
|
|
7305
|
+
return (
|
|
7306
|
+
<div>
|
|
7307
|
+
<React.Suspense fallback={<Loading />}>
|
|
7308
|
+
<MyComponent promise={query.promise} />
|
|
7309
|
+
</React.Suspense>
|
|
7310
|
+
<button onClick={() => queryClient.cancelQueries(options)}>
|
|
7311
|
+
cancel
|
|
7312
|
+
</button>
|
|
7313
|
+
</div>
|
|
7314
|
+
)
|
|
7315
|
+
}
|
|
7316
|
+
|
|
7317
|
+
queryClient.setQueryData(key, 'initial')
|
|
7318
|
+
|
|
7319
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
7320
|
+
fireEvent.click(rendered.getByText('cancel'))
|
|
7321
|
+
await waitFor(() => rendered.getByText('initial'))
|
|
7322
|
+
|
|
7323
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
7324
|
+
})
|
|
7325
|
+
|
|
7326
|
+
it('should suspend when not enabled', async () => {
|
|
7327
|
+
const key = queryKey()
|
|
7328
|
+
|
|
7329
|
+
const options = (count: number) => ({
|
|
7330
|
+
queryKey: [...key, count],
|
|
7331
|
+
queryFn: async () => {
|
|
7332
|
+
await sleep(10)
|
|
7333
|
+
return 'test' + count
|
|
7334
|
+
},
|
|
7335
|
+
})
|
|
7336
|
+
|
|
7337
|
+
function MyComponent(props: { promise: Promise<string> }) {
|
|
7338
|
+
const data = React.use(props.promise)
|
|
7339
|
+
|
|
7340
|
+
return <>{data}</>
|
|
7341
|
+
}
|
|
7342
|
+
|
|
7343
|
+
function Loading() {
|
|
7344
|
+
return <>loading..</>
|
|
7345
|
+
}
|
|
7346
|
+
function Page() {
|
|
7347
|
+
const [count, setCount] = React.useState(0)
|
|
7348
|
+
const query = useQuery({ ...options(count), enabled: count > 0 })
|
|
7349
|
+
|
|
7350
|
+
return (
|
|
7351
|
+
<div>
|
|
7352
|
+
<React.Suspense fallback={<Loading />}>
|
|
7353
|
+
<MyComponent promise={query.promise} />
|
|
7354
|
+
</React.Suspense>
|
|
7355
|
+
<button onClick={() => setCount(1)}>enable</button>
|
|
7356
|
+
</div>
|
|
7357
|
+
)
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7360
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
7361
|
+
await waitFor(() => rendered.getByText('loading..'))
|
|
7362
|
+
fireEvent.click(rendered.getByText('enable'))
|
|
7363
|
+
await waitFor(() => rendered.getByText('test1'))
|
|
7364
|
+
})
|
|
7365
|
+
})
|
|
6584
7366
|
})
|