@tanstack/react-query 5.0.0-alpha.70 → 5.0.0-alpha.81

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 (94) hide show
  1. package/build/codemods/remove-overloads/remove-overloads.js +1 -1
  2. package/build/codemods/src/v4/key-transformation.js +1 -1
  3. package/build/codemods/src/v4/replace-import-specifier.js +1 -1
  4. package/build/lib/QueryClientProvider.cjs +1 -1
  5. package/build/lib/QueryClientProvider.cjs.map +1 -1
  6. package/build/lib/QueryClientProvider.d.ts +0 -1
  7. package/build/lib/QueryClientProvider.d.ts.map +1 -1
  8. package/build/lib/QueryClientProvider.js +1 -1
  9. package/build/lib/QueryClientProvider.js.map +1 -1
  10. package/build/lib/QueryClientProvider.legacy.cjs +1 -1
  11. package/build/lib/QueryClientProvider.legacy.cjs.map +1 -1
  12. package/build/lib/QueryClientProvider.legacy.js +1 -1
  13. package/build/lib/QueryClientProvider.legacy.js.map +1 -1
  14. package/build/lib/useBaseQuery.cjs +1 -1
  15. package/build/lib/useBaseQuery.cjs.map +1 -1
  16. package/build/lib/useBaseQuery.d.ts +0 -1
  17. package/build/lib/useBaseQuery.d.ts.map +1 -1
  18. package/build/lib/useBaseQuery.js +1 -1
  19. package/build/lib/useBaseQuery.js.map +1 -1
  20. package/build/lib/useBaseQuery.legacy.cjs +1 -1
  21. package/build/lib/useBaseQuery.legacy.cjs.map +1 -1
  22. package/build/lib/useBaseQuery.legacy.js +1 -1
  23. package/build/lib/useBaseQuery.legacy.js.map +1 -1
  24. package/build/lib/useInfiniteQuery.cjs +1 -1
  25. package/build/lib/useInfiniteQuery.cjs.map +1 -1
  26. package/build/lib/useInfiniteQuery.d.ts +0 -1
  27. package/build/lib/useInfiniteQuery.d.ts.map +1 -1
  28. package/build/lib/useInfiniteQuery.js +1 -1
  29. package/build/lib/useInfiniteQuery.js.map +1 -1
  30. package/build/lib/useInfiniteQuery.legacy.cjs +1 -1
  31. package/build/lib/useInfiniteQuery.legacy.cjs.map +1 -1
  32. package/build/lib/useInfiniteQuery.legacy.js +1 -1
  33. package/build/lib/useInfiniteQuery.legacy.js.map +1 -1
  34. package/build/lib/useIsFetching.cjs +1 -1
  35. package/build/lib/useIsFetching.cjs.map +1 -1
  36. package/build/lib/useIsFetching.d.ts +0 -1
  37. package/build/lib/useIsFetching.d.ts.map +1 -1
  38. package/build/lib/useIsFetching.js +1 -1
  39. package/build/lib/useIsFetching.js.map +1 -1
  40. package/build/lib/useIsFetching.legacy.cjs +1 -1
  41. package/build/lib/useIsFetching.legacy.cjs.map +1 -1
  42. package/build/lib/useIsFetching.legacy.js +1 -1
  43. package/build/lib/useIsFetching.legacy.js.map +1 -1
  44. package/build/lib/useMutation.cjs +1 -1
  45. package/build/lib/useMutation.cjs.map +1 -1
  46. package/build/lib/useMutation.d.ts +0 -1
  47. package/build/lib/useMutation.d.ts.map +1 -1
  48. package/build/lib/useMutation.js +1 -1
  49. package/build/lib/useMutation.js.map +1 -1
  50. package/build/lib/useMutation.legacy.cjs +1 -1
  51. package/build/lib/useMutation.legacy.cjs.map +1 -1
  52. package/build/lib/useMutation.legacy.js +1 -1
  53. package/build/lib/useMutation.legacy.js.map +1 -1
  54. package/build/lib/useMutationState.cjs +1 -1
  55. package/build/lib/useMutationState.cjs.map +1 -1
  56. package/build/lib/useMutationState.d.ts +0 -1
  57. package/build/lib/useMutationState.d.ts.map +1 -1
  58. package/build/lib/useMutationState.js +1 -1
  59. package/build/lib/useMutationState.js.map +1 -1
  60. package/build/lib/useMutationState.legacy.cjs +1 -1
  61. package/build/lib/useMutationState.legacy.cjs.map +1 -1
  62. package/build/lib/useMutationState.legacy.js +1 -1
  63. package/build/lib/useMutationState.legacy.js.map +1 -1
  64. package/build/lib/useQueries.cjs +1 -1
  65. package/build/lib/useQueries.cjs.map +1 -1
  66. package/build/lib/useQueries.d.ts +0 -1
  67. package/build/lib/useQueries.d.ts.map +1 -1
  68. package/build/lib/useQueries.js +1 -1
  69. package/build/lib/useQueries.js.map +1 -1
  70. package/build/lib/useQueries.legacy.cjs +1 -1
  71. package/build/lib/useQueries.legacy.cjs.map +1 -1
  72. package/build/lib/useQueries.legacy.js +1 -1
  73. package/build/lib/useQueries.legacy.js.map +1 -1
  74. package/build/lib/useQuery.cjs +1 -1
  75. package/build/lib/useQuery.cjs.map +1 -1
  76. package/build/lib/useQuery.d.ts +0 -1
  77. package/build/lib/useQuery.d.ts.map +1 -1
  78. package/build/lib/useQuery.js +1 -1
  79. package/build/lib/useQuery.js.map +1 -1
  80. package/build/lib/useQuery.legacy.cjs +1 -1
  81. package/build/lib/useQuery.legacy.cjs.map +1 -1
  82. package/build/lib/useQuery.legacy.js +1 -1
  83. package/build/lib/useQuery.legacy.js.map +1 -1
  84. package/package.json +2 -2
  85. package/src/QueryClientProvider.tsx +1 -1
  86. package/src/__tests__/useInfiniteQuery.test.tsx +34 -72
  87. package/src/__tests__/useQuery.test.tsx +135 -34
  88. package/src/useBaseQuery.ts +1 -1
  89. package/src/useInfiniteQuery.ts +1 -1
  90. package/src/useIsFetching.ts +1 -1
  91. package/src/useMutation.ts +1 -1
  92. package/src/useMutationState.ts +1 -1
  93. package/src/useQueries.ts +1 -1
  94. package/src/useQuery.ts +1 -1
@@ -213,7 +213,7 @@ describe('useInfiniteQuery', () => {
213
213
 
214
214
  await waitFor(() => rendered.getByText('data: 0-asc'))
215
215
  await waitFor(() => rendered.getByText('isFetching: false'))
216
- await waitFor(() => expect(states.length).toBe(7))
216
+ await waitFor(() => expect(states.length).toBe(6))
217
217
 
218
218
  expect(states[0]).toMatchObject({
219
219
  data: undefined,
@@ -251,15 +251,7 @@ describe('useInfiniteQuery', () => {
251
251
  isSuccess: true,
252
252
  isPlaceholderData: true,
253
253
  })
254
- // Hook state update
255
254
  expect(states[5]).toMatchObject({
256
- data: { pages: ['0-desc', '1-desc'] },
257
- isFetching: true,
258
- isFetchingNextPage: false,
259
- isSuccess: true,
260
- isPlaceholderData: true,
261
- })
262
- expect(states[6]).toMatchObject({
263
255
  data: { pages: ['0-asc'] },
264
256
  isFetching: false,
265
257
  isFetchingNextPage: false,
@@ -606,77 +598,47 @@ describe('useInfiniteQuery', () => {
606
598
 
607
599
  it('should silently cancel any ongoing fetch when fetching more', async () => {
608
600
  const key = queryKey()
609
- const states: UseInfiniteQueryResult<InfiniteData<number>>[] = []
610
601
 
611
602
  function Page() {
612
603
  const start = 10
613
- const state = useInfiniteQuery({
614
- queryKey: key,
615
- queryFn: async ({ pageParam }) => {
616
- await sleep(50)
617
- return Number(pageParam)
618
- },
619
- defaultPageParam: start,
620
- getNextPageParam: (lastPage) => lastPage + 1,
621
- notifyOnChangeProps: 'all',
622
- })
623
-
624
- states.push(state)
625
-
626
- const { refetch, fetchNextPage } = state
627
-
628
- React.useEffect(() => {
629
- setActTimeout(() => {
630
- refetch()
631
- }, 100)
632
- setActTimeout(() => {
633
- fetchNextPage()
634
- }, 110)
635
- }, [fetchNextPage, refetch])
604
+ const { data, fetchNextPage, refetch, status, fetchStatus } =
605
+ useInfiniteQuery({
606
+ queryKey: key,
607
+ queryFn: async ({ pageParam }) => {
608
+ await sleep(50)
609
+ return Number(pageParam)
610
+ },
611
+ defaultPageParam: start,
612
+ getNextPageParam: (lastPage) => lastPage + 1,
613
+ })
636
614
 
637
- return null
615
+ return (
616
+ <div>
617
+ <button onClick={() => fetchNextPage()}>fetchNextPage</button>
618
+ <button onClick={() => refetch()}>refetch</button>
619
+ <div>data: {JSON.stringify(data)}</div>
620
+ <div>
621
+ status: {status}, {fetchStatus}
622
+ </div>
623
+ </div>
624
+ )
638
625
  }
639
626
 
640
- renderWithClient(queryClient, <Page />)
627
+ const rendered = renderWithClient(queryClient, <Page />)
641
628
 
642
- await sleep(300)
629
+ await waitFor(() => rendered.getByText('status: success, idle'))
630
+ await waitFor(() =>
631
+ rendered.getByText('data: {"pages":[10],"pageParams":[10]}'),
632
+ )
643
633
 
644
- expect(states.length).toBe(5)
645
- expect(states[0]).toMatchObject({
646
- hasNextPage: false,
647
- data: undefined,
648
- isFetching: true,
649
- isFetchingNextPage: false,
650
- isSuccess: false,
651
- })
652
- expect(states[1]).toMatchObject({
653
- hasNextPage: true,
654
- data: { pages: [10] },
655
- isFetching: false,
656
- isFetchingNextPage: false,
657
- isSuccess: true,
658
- })
659
- expect(states[2]).toMatchObject({
660
- hasNextPage: true,
661
- data: { pages: [10] },
662
- isFetching: true,
663
- isFetchingNextPage: false,
664
- isSuccess: true,
665
- })
666
- expect(states[3]).toMatchObject({
667
- hasNextPage: true,
668
- data: { pages: [10] },
669
- isFetching: true,
670
- isFetchingNextPage: true,
671
- isSuccess: true,
672
- })
673
- expect(states[4]).toMatchObject({
674
- hasNextPage: true,
675
- data: { pages: [10, 11] },
676
- isFetching: false,
677
- isFetchingNextPage: false,
678
- isSuccess: true,
679
- })
634
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
635
+ await waitFor(() => rendered.getByText('status: success, fetching'))
636
+ fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i }))
637
+
638
+ await waitFor(() => rendered.getByText('status: success, idle'))
639
+ await waitFor(() =>
640
+ rendered.getByText('data: {"pages":[10,11],"pageParams":[10,11]}'),
641
+ )
680
642
  })
681
643
 
682
644
  it('should silently cancel an ongoing fetchNextPage request when another fetchNextPage is invoked', async () => {
@@ -693,17 +693,15 @@ describe('useQuery', () => {
693
693
  // required to make sure no additional renders are happening after data is successfully fetched for the second time
694
694
  await sleep(100)
695
695
 
696
- expect(states.length).toBe(5)
696
+ expect(states.length).toBe(4)
697
697
  // First load
698
698
  expect(states[0]).toMatchObject({ isPending: true, isSuccess: false })
699
699
  // First success
700
700
  expect(states[1]).toMatchObject({ isPending: false, isSuccess: true })
701
701
  // Remove
702
702
  expect(states[2]).toMatchObject({ isPending: true, isSuccess: false })
703
- // Hook state update
704
- expect(states[3]).toMatchObject({ isPending: true, isSuccess: false })
705
703
  // Second success
706
- expect(states[4]).toMatchObject({ isPending: false, isSuccess: true })
704
+ expect(states[3]).toMatchObject({ isPending: false, isSuccess: true })
707
705
  })
708
706
 
709
707
  it('should fetch when refetchOnMount is false and nothing has been fetched yet', async () => {
@@ -1716,7 +1714,7 @@ describe('useQuery', () => {
1716
1714
  act(() => rendered.rerender(<Page count={2} />))
1717
1715
  await waitFor(() => rendered.getByText('error: Error test'))
1718
1716
 
1719
- await waitFor(() => expect(states.length).toBe(8))
1717
+ await waitFor(() => expect(states.length).toBe(6))
1720
1718
  // Initial
1721
1719
  expect(states[0]).toMatchObject({
1722
1720
  data: undefined,
@@ -1741,16 +1739,8 @@ describe('useQuery', () => {
1741
1739
  error: null,
1742
1740
  isPlaceholderData: true,
1743
1741
  })
1744
- // Hook state update
1745
- expect(states[3]).toMatchObject({
1746
- data: 0,
1747
- isFetching: true,
1748
- status: 'success',
1749
- error: null,
1750
- isPlaceholderData: true,
1751
- })
1752
1742
  // New data
1753
- expect(states[4]).toMatchObject({
1743
+ expect(states[3]).toMatchObject({
1754
1744
  data: 1,
1755
1745
  isFetching: false,
1756
1746
  status: 'success',
@@ -1758,15 +1748,7 @@ describe('useQuery', () => {
1758
1748
  isPlaceholderData: false,
1759
1749
  })
1760
1750
  // rerender Page 2
1761
- expect(states[5]).toMatchObject({
1762
- data: 1,
1763
- isFetching: true,
1764
- status: 'success',
1765
- error: null,
1766
- isPlaceholderData: true,
1767
- })
1768
- // Hook state update again
1769
- expect(states[6]).toMatchObject({
1751
+ expect(states[4]).toMatchObject({
1770
1752
  data: 1,
1771
1753
  isFetching: true,
1772
1754
  status: 'success',
@@ -1774,13 +1756,13 @@ describe('useQuery', () => {
1774
1756
  isPlaceholderData: true,
1775
1757
  })
1776
1758
  // Error
1777
- expect(states[7]).toMatchObject({
1759
+ expect(states[5]).toMatchObject({
1778
1760
  data: undefined,
1779
1761
  isFetching: false,
1780
1762
  status: 'error',
1781
1763
  isPlaceholderData: false,
1782
1764
  })
1783
- expect(states[7]?.error).toHaveProperty('message', 'Error test')
1765
+ expect(states[5]!.error).toHaveProperty('message', 'Error test')
1784
1766
  })
1785
1767
 
1786
1768
  it('should not show initial data from next query if placeholderData is set', async () => {
@@ -1825,7 +1807,7 @@ describe('useQuery', () => {
1825
1807
  rendered.getByText('data: 1, count: 1, isFetching: false'),
1826
1808
  )
1827
1809
 
1828
- expect(states.length).toBe(5)
1810
+ expect(states.length).toBe(4)
1829
1811
 
1830
1812
  // Initial
1831
1813
  expect(states[0]).toMatchObject({
@@ -1848,15 +1830,8 @@ describe('useQuery', () => {
1848
1830
  isSuccess: true,
1849
1831
  isPlaceholderData: false,
1850
1832
  })
1851
- // Hook state update
1852
- expect(states[3]).toMatchObject({
1853
- data: 99,
1854
- isFetching: true,
1855
- isSuccess: true,
1856
- isPlaceholderData: false,
1857
- })
1858
1833
  // New data
1859
- expect(states[4]).toMatchObject({
1834
+ expect(states[3]).toMatchObject({
1860
1835
  data: 1,
1861
1836
  isFetching: false,
1862
1837
  isSuccess: true,
@@ -3663,6 +3638,7 @@ describe('useQuery', () => {
3663
3638
  })
3664
3639
  act(() => setPrefetched(true))
3665
3640
  }
3641
+
3666
3642
  prefetch()
3667
3643
  }, [])
3668
3644
 
@@ -5929,6 +5905,7 @@ describe('useQuery', () => {
5929
5905
  </div>
5930
5906
  )
5931
5907
  }
5908
+
5932
5909
  const rendered = renderWithClient(queryClient, <Page />)
5933
5910
  const fetchBtn = rendered.getByRole('button', { name: 'refetch' })
5934
5911
  await waitFor(() => rendered.getByText('data: 1'))
@@ -5989,8 +5966,132 @@ describe('useQuery', () => {
5989
5966
  </div>
5990
5967
  )
5991
5968
  }
5969
+
5992
5970
  const rendered = renderWithClient(queryClient, <Page />)
5993
5971
  await waitFor(() => rendered.getByText('status: success'))
5994
5972
  await waitFor(() => rendered.getByText('data: 1'))
5995
5973
  })
5974
+ it('should reuse same data object reference when queryKey changes back to some cached data', async () => {
5975
+ const key = queryKey()
5976
+ const spy = vi.fn()
5977
+
5978
+ async function fetchNumber(id: number) {
5979
+ await sleep(5)
5980
+ return { numbers: { current: { id } } }
5981
+ }
5982
+ function Test() {
5983
+ const [id, setId] = React.useState(1)
5984
+
5985
+ const { data } = useQuery({
5986
+ select: selector,
5987
+ queryKey: [key, 'user', id],
5988
+ queryFn: () => fetchNumber(id),
5989
+ })
5990
+
5991
+ React.useEffect(() => {
5992
+ spy(data)
5993
+ }, [data])
5994
+
5995
+ return (
5996
+ <div>
5997
+ <button name="1" onClick={() => setId(1)}>
5998
+ 1
5999
+ </button>
6000
+ <button name="2" onClick={() => setId(2)}>
6001
+ 2
6002
+ </button>
6003
+ <span>Rendered Id: {data?.id}</span>
6004
+ </div>
6005
+ )
6006
+ }
6007
+
6008
+ function selector(data: any) {
6009
+ return data.numbers.current
6010
+ }
6011
+
6012
+ const rendered = renderWithClient(queryClient, <Test />)
6013
+ expect(spy).toHaveBeenCalledTimes(1)
6014
+
6015
+ spy.mockClear()
6016
+ await waitFor(() => rendered.getByText('Rendered Id: 1'))
6017
+ expect(spy).toHaveBeenCalledTimes(1)
6018
+
6019
+ spy.mockClear()
6020
+ fireEvent.click(rendered.getByRole('button', { name: /2/ }))
6021
+ await waitFor(() => rendered.getByText('Rendered Id: 2'))
6022
+ expect(spy).toHaveBeenCalledTimes(2) // called with undefined because id changed
6023
+
6024
+ spy.mockClear()
6025
+ fireEvent.click(rendered.getByRole('button', { name: /1/ }))
6026
+ await waitFor(() => rendered.getByText('Rendered Id: 1'))
6027
+ expect(spy).toHaveBeenCalledTimes(1)
6028
+
6029
+ spy.mockClear()
6030
+ fireEvent.click(rendered.getByRole('button', { name: /2/ }))
6031
+ await waitFor(() => rendered.getByText('Rendered Id: 2'))
6032
+ expect(spy).toHaveBeenCalledTimes(1)
6033
+ })
6034
+ it('should reuse same data object reference when queryKey changes and placeholderData is present', async () => {
6035
+ const key = queryKey()
6036
+ const spy = vi.fn()
6037
+
6038
+ async function fetchNumber(id: number) {
6039
+ await sleep(5)
6040
+ return { numbers: { current: { id } } }
6041
+ }
6042
+ function Test() {
6043
+ const [id, setId] = React.useState(1)
6044
+
6045
+ const { data } = useQuery({
6046
+ select: selector,
6047
+ queryKey: [key, 'user', id],
6048
+ queryFn: () => fetchNumber(id),
6049
+ placeholderData: { numbers: { current: { id: 99 } } },
6050
+ })
6051
+
6052
+ React.useEffect(() => {
6053
+ spy(data)
6054
+ }, [data])
6055
+
6056
+ return (
6057
+ <div>
6058
+ <button name="1" onClick={() => setId(1)}>
6059
+ 1
6060
+ </button>
6061
+ <button name="2" onClick={() => setId(2)}>
6062
+ 2
6063
+ </button>
6064
+ <span>Rendered Id: {data?.id}</span>
6065
+ </div>
6066
+ )
6067
+ }
6068
+
6069
+ function selector(data: any) {
6070
+ return data.numbers.current
6071
+ }
6072
+
6073
+ const rendered = renderWithClient(queryClient, <Test />)
6074
+ expect(spy).toHaveBeenCalledTimes(1)
6075
+
6076
+ spy.mockClear()
6077
+ await waitFor(() => rendered.getByText('Rendered Id: 99'))
6078
+ await waitFor(() => rendered.getByText('Rendered Id: 1'))
6079
+ expect(spy).toHaveBeenCalledTimes(1)
6080
+
6081
+ spy.mockClear()
6082
+ fireEvent.click(rendered.getByRole('button', { name: /2/ }))
6083
+ await waitFor(() => rendered.getByText('Rendered Id: 99'))
6084
+ await waitFor(() => rendered.getByText('Rendered Id: 2'))
6085
+ expect(spy).toHaveBeenCalledTimes(2) // called with undefined because id changed
6086
+
6087
+ spy.mockClear()
6088
+ fireEvent.click(rendered.getByRole('button', { name: /1/ }))
6089
+ await waitFor(() => rendered.getByText('Rendered Id: 1'))
6090
+ expect(spy).toHaveBeenCalledTimes(1)
6091
+
6092
+ spy.mockClear()
6093
+ fireEvent.click(rendered.getByRole('button', { name: /2/ }))
6094
+ await waitFor(() => rendered.getByText('Rendered Id: 2'))
6095
+ expect(spy).toHaveBeenCalledTimes(1)
6096
+ })
5996
6097
  })
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import * as React from 'react'
3
3
 
4
4
  import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import type {
3
3
  QueryObserver,
4
4
  QueryKey,
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import * as React from 'react'
3
3
  import type { QueryClient, QueryFilters } from '@tanstack/query-core'
4
4
  import { notifyManager } from '@tanstack/query-core'
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import * as React from 'react'
3
3
  import type { QueryClient, DefaultError } from '@tanstack/query-core'
4
4
  import { notifyManager, MutationObserver } from '@tanstack/query-core'
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import * as React from 'react'
3
3
 
4
4
  import type {
package/src/useQueries.ts CHANGED
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import * as React from 'react'
3
3
 
4
4
  import type {
package/src/useQuery.ts CHANGED
@@ -1,4 +1,4 @@
1
- import 'client-only'
1
+ 'use client'
2
2
  import type { QueryClient, QueryKey, DefaultError } from '@tanstack/query-core'
3
3
  import { QueryObserver } from '@tanstack/query-core'
4
4
  import type {