@tanstack/react-query 5.24.8 → 5.26.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.
Files changed (68) hide show
  1. package/build/codemods/coverage/clover.xml +2 -2
  2. package/build/codemods/coverage/index.html +1 -1
  3. package/build/codemods/coverage/utils/index.html +1 -1
  4. package/build/codemods/coverage/utils/index.js.html +1 -1
  5. package/build/codemods/coverage/utils/transformers/index.html +1 -1
  6. package/build/codemods/coverage/utils/transformers/query-cache-transformer.js.html +1 -1
  7. package/build/codemods/coverage/utils/transformers/query-client-transformer.js.html +1 -1
  8. package/build/codemods/coverage/utils/transformers/use-query-like-transformer.js.html +1 -1
  9. package/build/codemods/coverage/v4/index.html +1 -1
  10. package/build/codemods/coverage/v4/key-transformation.js.html +1 -1
  11. package/build/codemods/coverage/v4/replace-import-specifier.js.html +1 -1
  12. package/build/codemods/coverage/v4/utils/replacers/index.html +1 -1
  13. package/build/codemods/coverage/v4/utils/replacers/key-replacer.js.html +1 -1
  14. package/build/codemods/coverage/v5/is-loading/index.html +1 -1
  15. package/build/codemods/coverage/v5/is-loading/is-loading.js.html +1 -1
  16. package/build/codemods/coverage/v5/keep-previous-data/index.html +1 -1
  17. package/build/codemods/coverage/v5/keep-previous-data/keep-previous-data.js.html +1 -1
  18. package/build/codemods/coverage/v5/keep-previous-data/utils/already-has-placeholder-data-property.js.html +1 -1
  19. package/build/codemods/coverage/v5/keep-previous-data/utils/index.html +1 -1
  20. package/build/codemods/coverage/v5/remove-overloads/index.html +1 -1
  21. package/build/codemods/coverage/v5/remove-overloads/remove-overloads.js.html +1 -1
  22. package/build/codemods/coverage/v5/remove-overloads/transformers/filter-aware-usage-transformer.js.html +1 -1
  23. package/build/codemods/coverage/v5/remove-overloads/transformers/index.html +1 -1
  24. package/build/codemods/coverage/v5/remove-overloads/transformers/query-fn-aware-usage-transformer.js.html +1 -1
  25. package/build/codemods/coverage/v5/remove-overloads/utils/index.html +1 -1
  26. package/build/codemods/coverage/v5/remove-overloads/utils/index.js.html +1 -1
  27. package/build/codemods/coverage/v5/remove-overloads/utils/unknown-usage-error.js.html +1 -1
  28. package/build/codemods/coverage/v5/rename-hydrate/index.html +1 -1
  29. package/build/codemods/coverage/v5/rename-hydrate/rename-hydrate.js.html +1 -1
  30. package/build/codemods/coverage/v5/rename-properties/index.html +1 -1
  31. package/build/codemods/coverage/v5/rename-properties/rename-properties.js.html +1 -1
  32. package/build/legacy/useQueries.cjs.map +1 -1
  33. package/build/legacy/useQueries.d.cts +5 -5
  34. package/build/legacy/useQueries.d.ts +5 -5
  35. package/build/legacy/useQueries.js.map +1 -1
  36. package/build/legacy/useSuspenseQueries.cjs.map +1 -1
  37. package/build/legacy/useSuspenseQueries.d.cts +3 -3
  38. package/build/legacy/useSuspenseQueries.d.ts +3 -3
  39. package/build/legacy/useSuspenseQueries.js.map +1 -1
  40. package/build/legacy/useSuspenseQuery.cjs +5 -0
  41. package/build/legacy/useSuspenseQuery.cjs.map +1 -1
  42. package/build/legacy/useSuspenseQuery.js +6 -1
  43. package/build/legacy/useSuspenseQuery.js.map +1 -1
  44. package/build/modern/useQueries.cjs.map +1 -1
  45. package/build/modern/useQueries.d.cts +5 -5
  46. package/build/modern/useQueries.d.ts +5 -5
  47. package/build/modern/useQueries.js.map +1 -1
  48. package/build/modern/useSuspenseQueries.cjs.map +1 -1
  49. package/build/modern/useSuspenseQueries.d.cts +3 -3
  50. package/build/modern/useSuspenseQueries.d.ts +3 -3
  51. package/build/modern/useSuspenseQueries.js.map +1 -1
  52. package/build/modern/useSuspenseQuery.cjs +5 -0
  53. package/build/modern/useSuspenseQuery.cjs.map +1 -1
  54. package/build/modern/useSuspenseQuery.js +6 -1
  55. package/build/modern/useSuspenseQuery.js.map +1 -1
  56. package/build/query-codemods/vite.config.ts +1 -0
  57. package/package.json +2 -2
  58. package/src/__tests__/infiniteQueryOptions.test-d.tsx +4 -10
  59. package/src/__tests__/queryOptions.test-d.tsx +24 -2
  60. package/src/__tests__/suspense.test-d.tsx +11 -0
  61. package/src/__tests__/useInfiniteQuery.test.tsx +34 -58
  62. package/src/__tests__/useMutation.test.tsx +43 -62
  63. package/src/__tests__/useQueries.test.tsx +106 -104
  64. package/src/__tests__/useQuery.test.tsx +97 -47
  65. package/src/__tests__/utils.tsx +3 -10
  66. package/src/useQueries.ts +11 -7
  67. package/src/useSuspenseQueries.ts +7 -5
  68. package/src/useSuspenseQuery.ts +7 -1
@@ -1,9 +1,9 @@
1
1
  import { describe, expectTypeOf, it } from 'vitest'
2
2
  import { QueryClient } from '@tanstack/query-core'
3
+ import { type InfiniteData, dataTagSymbol } from '@tanstack/query-core'
3
4
  import { infiniteQueryOptions } from '../infiniteQueryOptions'
4
5
  import { useInfiniteQuery } from '../useInfiniteQuery'
5
6
  import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery'
6
- import type { InfiniteData, dataTagSymbol } from '@tanstack/query-core'
7
7
 
8
8
  describe('queryOptions', () => {
9
9
  it('should not allow excess properties', () => {
@@ -75,9 +75,7 @@ describe('queryOptions', () => {
75
75
  initialPageParam: 1,
76
76
  })
77
77
 
78
- expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf<
79
- InfiniteData<string>
80
- >()
78
+ expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<InfiniteData<string>>()
81
79
  })
82
80
  it('should tag the queryKey even if no promise is returned', () => {
83
81
  const { queryKey } = infiniteQueryOptions({
@@ -87,9 +85,7 @@ describe('queryOptions', () => {
87
85
  initialPageParam: 1,
88
86
  })
89
87
 
90
- expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf<
91
- InfiniteData<string>
92
- >()
88
+ expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<InfiniteData<string>>()
93
89
  })
94
90
  it('should tag the queryKey with the result type of the QueryFn if select is used', () => {
95
91
  const { queryKey } = infiniteQueryOptions({
@@ -100,9 +96,7 @@ describe('queryOptions', () => {
100
96
  initialPageParam: 1,
101
97
  })
102
98
 
103
- expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf<
104
- InfiniteData<string>
105
- >()
99
+ expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<InfiniteData<string>>()
106
100
  })
107
101
  it('should return the proper type when passed to getQueryData', () => {
108
102
  const { queryKey } = infiniteQueryOptions({
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, expectTypeOf, it } from 'vitest'
2
- import { QueryClient } from '@tanstack/query-core'
3
- import { dataTagSymbol } from '@tanstack/query-core'
2
+ import { QueryClient, dataTagSymbol, skipToken } from '@tanstack/query-core'
4
3
  import { queryOptions } from '../queryOptions'
5
4
  import { useQuery } from '../useQuery'
6
5
  import { useQueries } from '../useQueries'
@@ -43,6 +42,7 @@ describe('queryOptions', () => {
43
42
  const { data } = useSuspenseQuery(options)
44
43
  expectTypeOf(data).toEqualTypeOf<number>()
45
44
  })
45
+
46
46
  it('should work when passed to fetchQuery', async () => {
47
47
  const options = queryOptions({
48
48
  queryKey: ['key'],
@@ -147,4 +147,26 @@ describe('queryOptions', () => {
147
147
  const data = queryClient.setQueryData(queryKey, 5)
148
148
  expectTypeOf(data).toEqualTypeOf<number | undefined>()
149
149
  })
150
+
151
+ it('should infer even if there is a conditional skipToken', () => {
152
+ const options = queryOptions({
153
+ queryKey: ['key'],
154
+ queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
155
+ })
156
+
157
+ const queryClient = new QueryClient()
158
+ const data = queryClient.getQueryData(options.queryKey)
159
+ expectTypeOf(data).toEqualTypeOf<number | undefined>()
160
+ })
161
+
162
+ it('should infer to unknown if we disable a query with just a skipToken', () => {
163
+ const options = queryOptions({
164
+ queryKey: ['key'],
165
+ queryFn: skipToken,
166
+ })
167
+
168
+ const queryClient = new QueryClient()
169
+ const data = queryClient.getQueryData(options.queryKey)
170
+ expectTypeOf(data).toEqualTypeOf<unknown>()
171
+ })
150
172
  })
@@ -1,6 +1,7 @@
1
1
  import { describe, expectTypeOf, it } from 'vitest'
2
2
  import { useSuspenseQuery } from '../useSuspenseQuery'
3
3
  import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery'
4
+ import type { UseSuspenseQueryOptions } from '..'
4
5
  import type { InfiniteData } from '@tanstack/query-core'
5
6
 
6
7
  describe('useSuspenseQuery', () => {
@@ -121,4 +122,14 @@ describe('useSuspenseInfiniteQuery', () => {
121
122
  // @ts-expect-error TS2339
122
123
  query.isPlaceholderData
123
124
  })
125
+
126
+ it('should not accept skipToken type for queryFn in useSuspenseQuery', () => {
127
+ const query: UseSuspenseQueryOptions = {
128
+ // @ts-expect-error
129
+ queryFn: skipToken,
130
+ queryKey: [1],
131
+ }
132
+
133
+ return query
134
+ })
124
135
  })
@@ -885,7 +885,8 @@ describe('useInfiniteQuery', () => {
885
885
 
886
886
  it('should be able to set new pages with the query client', async () => {
887
887
  const key = queryKey()
888
- const states: Array<UseInfiniteQueryResult<InfiniteData<number>>> = []
888
+
889
+ let multiplier = 1
889
890
 
890
891
  function Page() {
891
892
  const [firstPage, setFirstPage] = React.useState(0)
@@ -894,75 +895,50 @@ describe('useInfiniteQuery', () => {
894
895
  queryKey: key,
895
896
  queryFn: async ({ pageParam }) => {
896
897
  await sleep(10)
897
- return Number(pageParam)
898
+ return Number(multiplier * pageParam)
898
899
  },
899
900
  getNextPageParam: (lastPage) => lastPage + 1,
900
901
  initialPageParam: firstPage,
901
- notifyOnChangeProps: 'all',
902
902
  })
903
903
 
904
- states.push(state)
904
+ return (
905
+ <div>
906
+ <button
907
+ onClick={() => {
908
+ queryClient.setQueryData(key, {
909
+ pages: [7, 8],
910
+ pageParams: [7, 8],
911
+ })
912
+ setFirstPage(7)
913
+ }}
914
+ >
915
+ setPages
916
+ </button>
917
+ <button onClick={() => state.refetch()}>refetch</button>
918
+ <div>data: {JSON.stringify(state.data)}</div>
919
+ </div>
920
+ )
921
+ }
905
922
 
906
- const { refetch } = state
923
+ const rendered = renderWithClient(queryClient, <Page />)
907
924
 
908
- React.useEffect(() => {
909
- setActTimeout(() => {
910
- queryClient.setQueryData(key, { pages: [7, 8], pageParams: [7, 8] })
911
- setFirstPage(7)
912
- }, 20)
925
+ await waitFor(() =>
926
+ rendered.getByText('data: {"pages":[0],"pageParams":[0]}'),
927
+ )
913
928
 
914
- setActTimeout(() => {
915
- refetch()
916
- }, 50)
917
- }, [refetch])
929
+ fireEvent.click(rendered.getByRole('button', { name: /setPages/i }))
918
930
 
919
- return null
920
- }
931
+ await waitFor(() =>
932
+ rendered.getByText('data: {"pages":[7,8],"pageParams":[7,8]}'),
933
+ )
921
934
 
922
- renderWithClient(queryClient, <Page />)
935
+ multiplier = 2
923
936
 
924
- await sleep(100)
937
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
925
938
 
926
- expect(states.length).toBe(5)
927
- expect(states[0]).toMatchObject({
928
- hasNextPage: false,
929
- data: undefined,
930
- isFetching: true,
931
- isFetchingNextPage: false,
932
- isSuccess: false,
933
- })
934
- // After first fetch
935
- expect(states[1]).toMatchObject({
936
- hasNextPage: true,
937
- data: { pages: [0] },
938
- isFetching: false,
939
- isFetchingNextPage: false,
940
- isSuccess: true,
941
- })
942
- // Set state
943
- expect(states[2]).toMatchObject({
944
- hasNextPage: true,
945
- data: { pages: [7, 8] },
946
- isFetching: false,
947
- isFetchingNextPage: false,
948
- isSuccess: true,
949
- })
950
- // Refetch
951
- expect(states[3]).toMatchObject({
952
- hasNextPage: true,
953
- data: { pages: [7, 8] },
954
- isFetching: true,
955
- isFetchingNextPage: false,
956
- isSuccess: true,
957
- })
958
- // Refetch done
959
- expect(states[4]).toMatchObject({
960
- hasNextPage: true,
961
- data: { pages: [7, 8] },
962
- isFetching: false,
963
- isFetchingNextPage: false,
964
- isSuccess: true,
965
- })
939
+ await waitFor(() =>
940
+ rendered.getByText('data: {"pages":[14,30],"pageParams":[7,15]}'),
941
+ )
966
942
  })
967
943
 
968
944
  it('should only refetch the first page when initialData is provided', async () => {
@@ -453,8 +453,6 @@ describe('useMutation', () => {
453
453
 
454
454
  const rendered = renderWithClient(queryClient, <Page />)
455
455
 
456
- window.dispatchEvent(new Event('offline'))
457
-
458
456
  await waitFor(() => {
459
457
  expect(
460
458
  rendered.getByText('error: null, status: idle, isPaused: false'),
@@ -471,8 +469,8 @@ describe('useMutation', () => {
471
469
 
472
470
  expect(count).toBe(0)
473
471
 
474
- onlineMock.mockRestore()
475
- window.dispatchEvent(new Event('online'))
472
+ onlineMock.mockReturnValue(true)
473
+ queryClient.getMutationCache().resumePausedMutations()
476
474
 
477
475
  await sleep(100)
478
476
 
@@ -483,6 +481,7 @@ describe('useMutation', () => {
483
481
  })
484
482
 
485
483
  expect(count).toBe(2)
484
+ onlineMock.mockRestore()
486
485
  })
487
486
 
488
487
  it('should call onMutate even if paused', async () => {
@@ -515,8 +514,6 @@ describe('useMutation', () => {
515
514
 
516
515
  await rendered.findByText('data: null, status: idle, isPaused: false')
517
516
 
518
- window.dispatchEvent(new Event('offline'))
519
-
520
517
  fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
521
518
 
522
519
  await rendered.findByText('data: null, status: pending, isPaused: true')
@@ -524,13 +521,15 @@ describe('useMutation', () => {
524
521
  expect(onMutate).toHaveBeenCalledTimes(1)
525
522
  expect(onMutate).toHaveBeenCalledWith('todo')
526
523
 
527
- onlineMock.mockRestore()
528
- window.dispatchEvent(new Event('online'))
524
+ onlineMock.mockReturnValue(true)
525
+ queryClient.getMutationCache().resumePausedMutations()
529
526
 
530
527
  await rendered.findByText('data: 1, status: success, isPaused: false')
531
528
 
532
529
  expect(onMutate).toHaveBeenCalledTimes(1)
533
530
  expect(count).toBe(1)
531
+
532
+ onlineMock.mockRestore()
534
533
  })
535
534
 
536
535
  it('should optimistically go to paused state if offline', async () => {
@@ -564,8 +563,6 @@ describe('useMutation', () => {
564
563
 
565
564
  await rendered.findByText('data: null, status: idle, isPaused: false')
566
565
 
567
- window.dispatchEvent(new Event('offline'))
568
-
569
566
  fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
570
567
 
571
568
  await rendered.findByText('data: null, status: pending, isPaused: true')
@@ -574,22 +571,25 @@ describe('useMutation', () => {
574
571
  expect(states[0]).toBe('idle, false')
575
572
  expect(states[1]).toBe('pending, true')
576
573
 
577
- onlineMock.mockRestore()
578
- window.dispatchEvent(new Event('online'))
574
+ onlineMock.mockReturnValue(true)
575
+ queryClient.getMutationCache().resumePausedMutations()
579
576
 
580
577
  await rendered.findByText('data: 1, status: success, isPaused: false')
578
+
579
+ onlineMock.mockRestore()
581
580
  })
582
581
 
583
582
  it('should be able to retry a mutation when online', async () => {
584
583
  const onlineMock = mockOnlineManagerIsOnline(false)
584
+ const key = queryKey()
585
585
 
586
586
  let count = 0
587
- const states: Array<UseMutationResult<any, any, any, any>> = []
588
587
 
589
588
  function Page() {
590
589
  const state = useMutation({
590
+ mutationKey: key,
591
591
  mutationFn: async (_text: string) => {
592
- await sleep(1)
592
+ await sleep(10)
593
593
  count++
594
594
  return count > 1
595
595
  ? Promise.resolve('data')
@@ -600,69 +600,50 @@ describe('useMutation', () => {
600
600
  networkMode: 'offlineFirst',
601
601
  })
602
602
 
603
- states.push(state)
604
-
605
- const { mutate } = state
606
-
607
- React.useEffect(() => {
608
- setActTimeout(() => {
609
- window.dispatchEvent(new Event('offline'))
610
- mutate('todo')
611
- }, 10)
612
- }, [mutate])
613
-
614
- return null
603
+ return (
604
+ <div>
605
+ <button onClick={() => state.mutate('todo')}>mutate</button>
606
+ <div>status: {state.status}</div>
607
+ <div>isPaused: {String(state.isPaused)}</div>
608
+ <div>data: {state.data ?? 'null'}</div>
609
+ </div>
610
+ )
615
611
  }
616
612
 
617
- renderWithClient(queryClient, <Page />)
618
-
619
- await sleep(50)
613
+ const rendered = renderWithClient(queryClient, <Page />)
620
614
 
621
- expect(states.length).toBe(4)
622
- expect(states[0]).toMatchObject({
623
- isPending: false,
624
- isPaused: false,
625
- failureCount: 0,
626
- failureReason: null,
627
- })
628
- expect(states[1]).toMatchObject({
629
- isPending: true,
630
- isPaused: false,
631
- failureCount: 0,
632
- failureReason: null,
633
- })
634
- expect(states[2]).toMatchObject({
635
- isPending: true,
636
- isPaused: false,
637
- failureCount: 1,
638
- failureReason: new Error('oops'),
639
- })
640
- expect(states[3]).toMatchObject({
641
- isPending: true,
615
+ await waitFor(() => rendered.getByText('status: idle'))
616
+ fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
617
+ await waitFor(() => rendered.getByText('isPaused: true'))
618
+
619
+ expect(
620
+ queryClient.getMutationCache().findAll({ mutationKey: key }).length,
621
+ ).toBe(1)
622
+ expect(
623
+ queryClient.getMutationCache().findAll({ mutationKey: key })[0]?.state,
624
+ ).toMatchObject({
625
+ status: 'pending',
642
626
  isPaused: true,
643
627
  failureCount: 1,
644
628
  failureReason: new Error('oops'),
645
629
  })
646
630
 
647
- onlineMock.mockRestore()
648
- window.dispatchEvent(new Event('online'))
631
+ onlineMock.mockReturnValue(true)
632
+ queryClient.getMutationCache().resumePausedMutations()
649
633
 
650
- await sleep(50)
634
+ await waitFor(() => rendered.getByText('data: data'))
651
635
 
652
- expect(states.length).toBe(6)
653
- expect(states[4]).toMatchObject({
654
- isPending: true,
655
- isPaused: false,
656
- failureCount: 1,
657
- failureReason: new Error('oops'),
658
- })
659
- expect(states[5]).toMatchObject({
660
- isPending: false,
636
+ expect(
637
+ queryClient.getMutationCache().findAll({ mutationKey: key })[0]?.state,
638
+ ).toMatchObject({
639
+ status: 'success',
661
640
  isPaused: false,
662
641
  failureCount: 0,
663
642
  failureReason: null,
664
643
  data: 'data',
665
644
  })
645
+
646
+ onlineMock.mockRestore()
666
647
  })
667
648
 
668
649
  it('should not change state if unmounted', async () => {