@tanstack/react-query 4.13.4 → 4.14.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/lib/QueryClientProvider.esm.js +5 -0
- package/build/lib/QueryClientProvider.esm.js.map +1 -1
- package/build/lib/QueryClientProvider.js +5 -0
- package/build/lib/QueryClientProvider.js.map +1 -1
- package/build/lib/QueryClientProvider.mjs +5 -0
- package/build/lib/QueryClientProvider.mjs.map +1 -1
- package/build/umd/index.development.js +16 -3
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/QueryClientProvider.tsx +8 -0
- package/src/__tests__/ssr-hydration.test.tsx +24 -4
- package/src/__tests__/useQuery.test.tsx +173 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react-error-boundary": "^3.1.4"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@tanstack/query-core": "4.
|
|
49
|
+
"@tanstack/query-core": "4.14.0",
|
|
50
50
|
"use-sync-external-store": "^1.2.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
@@ -80,6 +80,14 @@ export const QueryClientProvider = ({
|
|
|
80
80
|
}
|
|
81
81
|
}, [client])
|
|
82
82
|
|
|
83
|
+
if (process.env.NODE_ENV !== 'production' && contextSharing) {
|
|
84
|
+
client
|
|
85
|
+
.getLogger()
|
|
86
|
+
.error(
|
|
87
|
+
`The contextSharing option has been deprecated and will be removed in the next major version`,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
const Context = getQueryClientContext(context, contextSharing)
|
|
84
92
|
|
|
85
93
|
return (
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
dehydrate,
|
|
13
13
|
hydrate,
|
|
14
14
|
} from '..'
|
|
15
|
-
import { createQueryClient,
|
|
15
|
+
import { createQueryClient, setIsServer, sleep } from './utils'
|
|
16
16
|
|
|
17
17
|
const isReact18 = () => (process.env.REACTJS_VERSION || '18') === '18'
|
|
18
18
|
|
|
@@ -55,6 +55,9 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
55
55
|
globalThis.IS_REACT_ACT_ENVIRONMENT = previousIsReactActEnvironment
|
|
56
56
|
})
|
|
57
57
|
it('should not mismatch on success', async () => {
|
|
58
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
59
|
+
consoleMock.mockImplementation(() => undefined)
|
|
60
|
+
|
|
58
61
|
if (!isReact18()) {
|
|
59
62
|
return
|
|
60
63
|
}
|
|
@@ -118,15 +121,23 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
118
121
|
)
|
|
119
122
|
|
|
120
123
|
// Check that we have no React hydration mismatches
|
|
121
|
-
|
|
124
|
+
// this should be zero calls and can be changed once we drop react17 support
|
|
125
|
+
expect(consoleMock).toHaveBeenNthCalledWith(
|
|
126
|
+
1,
|
|
127
|
+
'Warning: You are importing hydrateRoot from "react-dom" which is not supported. You should instead import it from "react-dom/client".',
|
|
128
|
+
)
|
|
122
129
|
expect(fetchDataSuccess).toHaveBeenCalledTimes(2)
|
|
123
130
|
expect(el.innerHTML).toBe(expectedMarkup)
|
|
124
131
|
|
|
125
132
|
unmount()
|
|
126
133
|
queryClient.clear()
|
|
134
|
+
consoleMock.mockRestore()
|
|
127
135
|
})
|
|
128
136
|
|
|
129
137
|
it('should not mismatch on error', async () => {
|
|
138
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
139
|
+
consoleMock.mockImplementation(() => undefined)
|
|
140
|
+
|
|
130
141
|
if (!isReact18()) {
|
|
131
142
|
return
|
|
132
143
|
}
|
|
@@ -187,7 +198,7 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
187
198
|
)
|
|
188
199
|
|
|
189
200
|
// We expect exactly one console.error here, which is from the
|
|
190
|
-
expect(
|
|
201
|
+
expect(consoleMock).toHaveBeenCalledTimes(1)
|
|
191
202
|
expect(fetchDataError).toHaveBeenCalledTimes(2)
|
|
192
203
|
expect(el.innerHTML).toBe(expectedMarkup)
|
|
193
204
|
await sleep(50)
|
|
@@ -198,9 +209,13 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
198
209
|
|
|
199
210
|
unmount()
|
|
200
211
|
queryClient.clear()
|
|
212
|
+
consoleMock.mockRestore()
|
|
201
213
|
})
|
|
202
214
|
|
|
203
215
|
it('should not mismatch on queries that were not prefetched', async () => {
|
|
216
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
217
|
+
consoleMock.mockImplementation(() => undefined)
|
|
218
|
+
|
|
204
219
|
if (!isReact18()) {
|
|
205
220
|
return
|
|
206
221
|
}
|
|
@@ -254,7 +269,11 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
254
269
|
)
|
|
255
270
|
|
|
256
271
|
// Check that we have no React hydration mismatches
|
|
257
|
-
|
|
272
|
+
// this should be zero calls and can be changed once we drop react17 support
|
|
273
|
+
expect(consoleMock).toHaveBeenNthCalledWith(
|
|
274
|
+
1,
|
|
275
|
+
'Warning: You are importing hydrateRoot from "react-dom" which is not supported. You should instead import it from "react-dom/client".',
|
|
276
|
+
)
|
|
258
277
|
expect(fetchDataSuccess).toHaveBeenCalledTimes(1)
|
|
259
278
|
expect(el.innerHTML).toBe(expectedMarkup)
|
|
260
279
|
await sleep(50)
|
|
@@ -265,5 +284,6 @@ describe('Server side rendering with de/rehydration', () => {
|
|
|
265
284
|
|
|
266
285
|
unmount()
|
|
267
286
|
queryClient.clear()
|
|
287
|
+
consoleMock.mockRestore()
|
|
268
288
|
})
|
|
269
289
|
})
|
|
@@ -873,20 +873,35 @@ describe('useQuery', () => {
|
|
|
873
873
|
|
|
874
874
|
states.push(state)
|
|
875
875
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
setActTimeout(() => {
|
|
880
|
-
remove()
|
|
881
|
-
rerender({})
|
|
882
|
-
}, 20)
|
|
883
|
-
}, [remove])
|
|
876
|
+
return (
|
|
877
|
+
<>
|
|
878
|
+
<div>{state.data}</div>
|
|
884
879
|
|
|
885
|
-
|
|
880
|
+
<button
|
|
881
|
+
onClick={() => {
|
|
882
|
+
state.remove()
|
|
883
|
+
rerender({})
|
|
884
|
+
}}
|
|
885
|
+
>
|
|
886
|
+
remove
|
|
887
|
+
</button>
|
|
888
|
+
</>
|
|
889
|
+
)
|
|
886
890
|
}
|
|
887
891
|
|
|
888
|
-
renderWithClient(queryClient, <Page />)
|
|
892
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
893
|
+
|
|
894
|
+
await waitFor(() => {
|
|
895
|
+
rendered.getByText('data')
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
fireEvent.click(rendered.getByRole('button', { name: 'remove' }))
|
|
889
899
|
|
|
900
|
+
await waitFor(() => {
|
|
901
|
+
rendered.getByText('data')
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
// required to make sure no additional renders are happening after data is successfully fetched for the second time
|
|
890
905
|
await sleep(100)
|
|
891
906
|
|
|
892
907
|
expect(states.length).toBe(5)
|
|
@@ -1518,17 +1533,27 @@ describe('useQuery', () => {
|
|
|
1518
1533
|
|
|
1519
1534
|
states.push(state)
|
|
1520
1535
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
setCount(1)
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1536
|
+
return (
|
|
1537
|
+
<div>
|
|
1538
|
+
<button onClick={() => setCount(1)}>increment</button>
|
|
1539
|
+
<div>data: {state.data ?? 'undefined'}</div>
|
|
1540
|
+
<div>count: {count}</div>
|
|
1541
|
+
</div>
|
|
1542
|
+
)
|
|
1528
1543
|
}
|
|
1529
1544
|
|
|
1530
|
-
renderWithClient(queryClient, <Page />)
|
|
1545
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1546
|
+
|
|
1547
|
+
await waitFor(() => rendered.getByText('data: 0'))
|
|
1548
|
+
|
|
1549
|
+
fireEvent.click(rendered.getByRole('button', { name: /increment/i }))
|
|
1531
1550
|
|
|
1551
|
+
await waitFor(() => {
|
|
1552
|
+
rendered.getByText('count: 1')
|
|
1553
|
+
rendered.getByText('data: undefined')
|
|
1554
|
+
})
|
|
1555
|
+
|
|
1556
|
+
// making sure no additional fetches are triggered
|
|
1532
1557
|
await sleep(50)
|
|
1533
1558
|
|
|
1534
1559
|
expect(states.length).toBe(3)
|
|
@@ -1744,18 +1769,30 @@ describe('useQuery', () => {
|
|
|
1744
1769
|
|
|
1745
1770
|
states.push(state)
|
|
1746
1771
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1772
|
+
return (
|
|
1773
|
+
<div>
|
|
1774
|
+
<h1>
|
|
1775
|
+
data: {state.data}, count: {count}, isFetching:{' '}
|
|
1776
|
+
{String(state.isFetching)}
|
|
1777
|
+
</h1>
|
|
1778
|
+
<button onClick={() => setCount(1)}>inc</button>
|
|
1779
|
+
</div>
|
|
1780
|
+
)
|
|
1754
1781
|
}
|
|
1755
1782
|
|
|
1756
|
-
renderWithClient(queryClient, <Page />)
|
|
1783
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1757
1784
|
|
|
1758
|
-
await waitFor(() =>
|
|
1785
|
+
await waitFor(() =>
|
|
1786
|
+
rendered.getByText('data: 0, count: 0, isFetching: false'),
|
|
1787
|
+
)
|
|
1788
|
+
|
|
1789
|
+
fireEvent.click(rendered.getByRole('button', { name: 'inc' }))
|
|
1790
|
+
|
|
1791
|
+
await waitFor(() =>
|
|
1792
|
+
rendered.getByText('data: 1, count: 1, isFetching: false'),
|
|
1793
|
+
)
|
|
1794
|
+
|
|
1795
|
+
expect(states.length).toBe(5)
|
|
1759
1796
|
|
|
1760
1797
|
// Initial
|
|
1761
1798
|
expect(states[0]).toMatchObject({
|
|
@@ -1773,17 +1810,17 @@ describe('useQuery', () => {
|
|
|
1773
1810
|
})
|
|
1774
1811
|
// Set state
|
|
1775
1812
|
expect(states[2]).toMatchObject({
|
|
1776
|
-
data:
|
|
1813
|
+
data: 99,
|
|
1777
1814
|
isFetching: true,
|
|
1778
1815
|
isSuccess: true,
|
|
1779
|
-
isPreviousData:
|
|
1816
|
+
isPreviousData: false,
|
|
1780
1817
|
})
|
|
1781
1818
|
// Hook state update
|
|
1782
1819
|
expect(states[3]).toMatchObject({
|
|
1783
|
-
data:
|
|
1820
|
+
data: 99,
|
|
1784
1821
|
isFetching: true,
|
|
1785
1822
|
isSuccess: true,
|
|
1786
|
-
isPreviousData:
|
|
1823
|
+
isPreviousData: false,
|
|
1787
1824
|
})
|
|
1788
1825
|
// New data
|
|
1789
1826
|
expect(states[4]).toMatchObject({
|
|
@@ -1812,26 +1849,41 @@ describe('useQuery', () => {
|
|
|
1812
1849
|
|
|
1813
1850
|
states.push(state)
|
|
1814
1851
|
|
|
1815
|
-
|
|
1852
|
+
return (
|
|
1853
|
+
<div>
|
|
1854
|
+
<button onClick={() => state.refetch()}>refetch</button>
|
|
1855
|
+
<button onClick={() => setCount(1)}>setCount</button>
|
|
1856
|
+
<div>data: {state.data ?? 'undefined'}</div>
|
|
1857
|
+
</div>
|
|
1858
|
+
)
|
|
1859
|
+
}
|
|
1816
1860
|
|
|
1817
|
-
|
|
1818
|
-
refetch()
|
|
1861
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1819
1862
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1863
|
+
await waitFor(() => {
|
|
1864
|
+
rendered.getByText('data: undefined')
|
|
1865
|
+
})
|
|
1823
1866
|
|
|
1824
|
-
|
|
1825
|
-
refetch()
|
|
1826
|
-
}, 30)
|
|
1827
|
-
}, [refetch])
|
|
1867
|
+
fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
|
|
1828
1868
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1869
|
+
await waitFor(() => {
|
|
1870
|
+
rendered.getByText('data: 0')
|
|
1871
|
+
})
|
|
1831
1872
|
|
|
1832
|
-
|
|
1873
|
+
fireEvent.click(rendered.getByRole('button', { name: 'setCount' }))
|
|
1833
1874
|
|
|
1834
|
-
await
|
|
1875
|
+
await waitFor(() => {
|
|
1876
|
+
rendered.getByText('data: 0')
|
|
1877
|
+
})
|
|
1878
|
+
|
|
1879
|
+
fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
|
|
1880
|
+
|
|
1881
|
+
await waitFor(() => {
|
|
1882
|
+
rendered.getByText('data: 1')
|
|
1883
|
+
})
|
|
1884
|
+
|
|
1885
|
+
// making sure no additional renders are triggered
|
|
1886
|
+
await sleep(20)
|
|
1835
1887
|
|
|
1836
1888
|
expect(states.length).toBe(6)
|
|
1837
1889
|
|
|
@@ -2194,19 +2246,31 @@ describe('useQuery', () => {
|
|
|
2194
2246
|
|
|
2195
2247
|
states.push(state)
|
|
2196
2248
|
|
|
2197
|
-
|
|
2249
|
+
return (
|
|
2250
|
+
<>
|
|
2251
|
+
<button
|
|
2252
|
+
onClick={async () => {
|
|
2253
|
+
await state.refetch()
|
|
2254
|
+
}}
|
|
2255
|
+
>
|
|
2256
|
+
refetch
|
|
2257
|
+
</button>
|
|
2198
2258
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
}, 10)
|
|
2203
|
-
}, [refetch])
|
|
2204
|
-
return null
|
|
2259
|
+
<div>{state.data}</div>
|
|
2260
|
+
</>
|
|
2261
|
+
)
|
|
2205
2262
|
}
|
|
2206
2263
|
|
|
2207
|
-
renderWithClient(queryClient, <Page />)
|
|
2264
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
2265
|
+
|
|
2266
|
+
await waitFor(() => {
|
|
2267
|
+
rendered.getByText('test')
|
|
2268
|
+
})
|
|
2208
2269
|
|
|
2209
|
-
|
|
2270
|
+
fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
|
|
2271
|
+
|
|
2272
|
+
// sleep is required to make sure no additional renders happen after click
|
|
2273
|
+
await sleep(20)
|
|
2210
2274
|
|
|
2211
2275
|
expect(states.length).toBe(2)
|
|
2212
2276
|
expect(states[0]).toMatchObject({
|
|
@@ -3681,6 +3745,61 @@ describe('useQuery', () => {
|
|
|
3681
3745
|
expect(results[1]).toMatchObject({ data: 1, isFetching: false })
|
|
3682
3746
|
})
|
|
3683
3747
|
|
|
3748
|
+
it('should show the correct data when switching keys with initialData, keepPreviousData & staleTime', async () => {
|
|
3749
|
+
const key = queryKey()
|
|
3750
|
+
|
|
3751
|
+
const ALL_TODOS = [
|
|
3752
|
+
{ name: 'todo A', priority: 'high' },
|
|
3753
|
+
{ name: 'todo B', priority: 'medium' },
|
|
3754
|
+
]
|
|
3755
|
+
|
|
3756
|
+
const initialTodos = ALL_TODOS
|
|
3757
|
+
|
|
3758
|
+
function Page() {
|
|
3759
|
+
const [filter, setFilter] = React.useState('')
|
|
3760
|
+
const { data: todos } = useQuery(
|
|
3761
|
+
[...key, filter],
|
|
3762
|
+
async () => {
|
|
3763
|
+
return ALL_TODOS.filter((todo) =>
|
|
3764
|
+
filter ? todo.priority === filter : true,
|
|
3765
|
+
)
|
|
3766
|
+
},
|
|
3767
|
+
{
|
|
3768
|
+
initialData() {
|
|
3769
|
+
return filter === '' ? initialTodos : undefined
|
|
3770
|
+
},
|
|
3771
|
+
keepPreviousData: true,
|
|
3772
|
+
staleTime: 5000,
|
|
3773
|
+
},
|
|
3774
|
+
)
|
|
3775
|
+
|
|
3776
|
+
return (
|
|
3777
|
+
<div>
|
|
3778
|
+
Current Todos, filter: {filter || 'all'}
|
|
3779
|
+
<hr />
|
|
3780
|
+
<button onClick={() => setFilter('')}>All</button>
|
|
3781
|
+
<button onClick={() => setFilter('high')}>High</button>
|
|
3782
|
+
<ul>
|
|
3783
|
+
{(todos ?? []).map((todo) => (
|
|
3784
|
+
<li key={todo.name}>
|
|
3785
|
+
{todo.name} - {todo.priority}
|
|
3786
|
+
</li>
|
|
3787
|
+
))}
|
|
3788
|
+
</ul>
|
|
3789
|
+
</div>
|
|
3790
|
+
)
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
3794
|
+
|
|
3795
|
+
await waitFor(() => rendered.getByText('Current Todos, filter: all'))
|
|
3796
|
+
|
|
3797
|
+
fireEvent.click(rendered.getByRole('button', { name: /high/i }))
|
|
3798
|
+
await waitFor(() => rendered.getByText('Current Todos, filter: high'))
|
|
3799
|
+
fireEvent.click(rendered.getByRole('button', { name: /all/i }))
|
|
3800
|
+
await waitFor(() => rendered.getByText('todo B - medium'))
|
|
3801
|
+
})
|
|
3802
|
+
|
|
3684
3803
|
// // See https://github.com/tannerlinsley/react-query/issues/214
|
|
3685
3804
|
it('data should persist when enabled is changed to false', async () => {
|
|
3686
3805
|
const key = queryKey()
|