@tanstack/react-query 4.40.1 → 4.41.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/__tests__/infiniteQueryOptions.test.d.ts +2 -0
- package/build/lib/__tests__/infiniteQueryOptions.types.test.d.ts +2 -0
- package/build/lib/__tests__/useSuspenseInfiniteQuery.types.test.d.ts +2 -0
- package/build/lib/index.d.ts +3 -0
- package/build/lib/index.esm.js +2 -0
- package/build/lib/index.esm.js.map +1 -1
- package/build/lib/index.js +4 -0
- package/build/lib/index.js.map +1 -1
- package/build/lib/index.mjs +2 -0
- package/build/lib/index.mjs.map +1 -1
- package/build/lib/infiniteQueryOptions.d.ts +14 -0
- package/build/lib/infiniteQueryOptions.esm.js +6 -0
- package/build/lib/infiniteQueryOptions.esm.js.map +1 -0
- package/build/lib/infiniteQueryOptions.js +10 -0
- package/build/lib/infiniteQueryOptions.js.map +1 -0
- package/build/lib/infiniteQueryOptions.mjs +6 -0
- package/build/lib/infiniteQueryOptions.mjs.map +1 -0
- package/build/lib/queryOptions.d.ts +2 -2
- package/build/lib/queryOptions.esm.js.map +1 -1
- package/build/lib/queryOptions.js.map +1 -1
- package/build/lib/queryOptions.mjs.map +1 -1
- package/build/lib/suspense.d.ts +7 -0
- package/build/lib/suspense.esm.js +11 -0
- package/build/lib/suspense.esm.js.map +1 -1
- package/build/lib/suspense.js +11 -0
- package/build/lib/suspense.js.map +1 -1
- package/build/lib/suspense.mjs +11 -0
- package/build/lib/suspense.mjs.map +1 -1
- package/build/lib/types.d.ts +3 -1
- package/build/lib/useInfiniteQuery.d.ts +7 -2
- package/build/lib/useInfiniteQuery.esm.js.map +1 -1
- package/build/lib/useInfiniteQuery.js.map +1 -1
- package/build/lib/useInfiniteQuery.mjs.map +1 -1
- package/build/lib/useQuery.d.ts +6 -6
- package/build/lib/useQuery.esm.js.map +1 -1
- package/build/lib/useQuery.js.map +1 -1
- package/build/lib/useQuery.mjs.map +1 -1
- package/build/lib/useSuspenseInfiniteQuery.d.ts +5 -0
- package/build/lib/useSuspenseInfiniteQuery.esm.js +14 -0
- package/build/lib/useSuspenseInfiniteQuery.esm.js.map +1 -0
- package/build/lib/useSuspenseInfiniteQuery.js +18 -0
- package/build/lib/useSuspenseInfiniteQuery.js.map +1 -0
- package/build/lib/useSuspenseInfiniteQuery.mjs +14 -0
- package/build/lib/useSuspenseInfiniteQuery.mjs.map +1 -0
- package/build/umd/index.development.js +26 -0
- 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/__tests__/infiniteQueryOptions.test.tsx +13 -0
- package/src/__tests__/infiniteQueryOptions.types.test.tsx +111 -0
- package/src/__tests__/queryOptions.types.test.tsx +8 -7
- package/src/__tests__/suspense.test.tsx +230 -0
- package/src/__tests__/useSuspenseInfiniteQuery.types.test.tsx +115 -0
- package/src/index.ts +6 -0
- package/src/infiniteQueryOptions.ts +95 -0
- package/src/queryOptions.ts +8 -2
- package/src/suspense.ts +11 -0
- package/src/types.ts +14 -0
- package/src/useInfiniteQuery.ts +28 -1
- package/src/useQuery.ts +6 -6
- package/src/useSuspenseInfiniteQuery.ts +62 -0
- package/build/lib/Hydrate.d.ts.map +0 -1
- package/build/lib/QueryClientProvider.d.ts.map +0 -1
- package/build/lib/QueryErrorResetBoundary.d.ts.map +0 -1
- package/build/lib/__tests__/Hydrate.test.d.ts.map +0 -1
- package/build/lib/__tests__/QueryClientProvider.test.d.ts.map +0 -1
- package/build/lib/__tests__/QueryResetErrorBoundary.test.d.ts.map +0 -1
- package/build/lib/__tests__/queryOptions.types.test.d.ts.map +0 -1
- package/build/lib/__tests__/ssr-hydration.test.d.ts.map +0 -1
- package/build/lib/__tests__/ssr.test.d.ts.map +0 -1
- package/build/lib/__tests__/suspense.test.d.ts.map +0 -1
- package/build/lib/__tests__/useInfiniteQuery.test.d.ts.map +0 -1
- package/build/lib/__tests__/useIsFetching.test.d.ts.map +0 -1
- package/build/lib/__tests__/useIsMutating.test.d.ts.map +0 -1
- package/build/lib/__tests__/useMutation.test.d.ts.map +0 -1
- package/build/lib/__tests__/useQueries.test.d.ts.map +0 -1
- package/build/lib/__tests__/useQuery.test.d.ts.map +0 -1
- package/build/lib/__tests__/useQuery.types.test.d.ts.map +0 -1
- package/build/lib/__tests__/useSuspenseQueries.types.test.d.ts.map +0 -1
- package/build/lib/__tests__/useSuspenseQuery.types.test.d.ts.map +0 -1
- package/build/lib/__tests__/utils.d.ts.map +0 -1
- package/build/lib/errorBoundaryUtils.d.ts.map +0 -1
- package/build/lib/index.d.ts.map +0 -1
- package/build/lib/isRestoring.d.ts.map +0 -1
- package/build/lib/queryOptions.d.ts.map +0 -1
- package/build/lib/reactBatchedUpdates.d.ts.map +0 -1
- package/build/lib/reactBatchedUpdates.native.d.ts.map +0 -1
- package/build/lib/setBatchUpdatesFn.d.ts.map +0 -1
- package/build/lib/suspense.d.ts.map +0 -1
- package/build/lib/types.d.ts.map +0 -1
- package/build/lib/useBaseQuery.d.ts.map +0 -1
- package/build/lib/useInfiniteQuery.d.ts.map +0 -1
- package/build/lib/useIsFetching.d.ts.map +0 -1
- package/build/lib/useIsMutating.d.ts.map +0 -1
- package/build/lib/useMutation.d.ts.map +0 -1
- package/build/lib/useQueries.d.ts.map +0 -1
- package/build/lib/useQuery.d.ts.map +0 -1
- package/build/lib/useSuspenseQueries.d.ts.map +0 -1
- package/build/lib/useSuspenseQuery.d.ts.map +0 -1
- package/build/lib/useSyncExternalStore.d.ts.map +0 -1
- package/build/lib/useSyncExternalStore.native.d.ts.map +0 -1
- package/build/lib/utils.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.41.0",
|
|
4
4
|
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"use-sync-external-store": "^1.2.0",
|
|
54
|
-
"@tanstack/query-core": "4.
|
|
54
|
+
"@tanstack/query-core": "4.41.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { infiniteQueryOptions } from '../infiniteQueryOptions'
|
|
2
|
+
|
|
3
|
+
describe('infiniteQueryOptions', () => {
|
|
4
|
+
it('should return the object received as a parameter without any modification.', () => {
|
|
5
|
+
const object = {
|
|
6
|
+
queryKey: ['key'],
|
|
7
|
+
queryFn: () => Promise.resolve(5),
|
|
8
|
+
getNextPageParam: () => null,
|
|
9
|
+
} as const
|
|
10
|
+
|
|
11
|
+
expect(infiniteQueryOptions(object)).toStrictEqual(object)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { expectTypeOf } from 'expect-type'
|
|
2
|
+
import {
|
|
3
|
+
type InfiniteData,
|
|
4
|
+
type UseInfiniteQueryResult,
|
|
5
|
+
useInfiniteQuery,
|
|
6
|
+
useQueryClient,
|
|
7
|
+
} from '@tanstack/react-query'
|
|
8
|
+
|
|
9
|
+
import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery'
|
|
10
|
+
import { infiniteQueryOptions } from '../infiniteQueryOptions'
|
|
11
|
+
import { doNotExecute } from './utils'
|
|
12
|
+
import type {
|
|
13
|
+
DefinedUseInfiniteQueryResult,
|
|
14
|
+
UseSuspenseInfiniteQueryResult,
|
|
15
|
+
} from '../types'
|
|
16
|
+
|
|
17
|
+
const infiniteQuery = {
|
|
18
|
+
options: () =>
|
|
19
|
+
infiniteQueryOptions({
|
|
20
|
+
queryKey: ['key', 1] as const,
|
|
21
|
+
queryFn: () => Promise.resolve({ field: 'success' }),
|
|
22
|
+
}),
|
|
23
|
+
optionsWithInitialData: () =>
|
|
24
|
+
infiniteQueryOptions({
|
|
25
|
+
queryKey: ['key', 2] as const,
|
|
26
|
+
queryFn: () => Promise.resolve({ field: 'success' }),
|
|
27
|
+
initialData: () => ({ pageParams: [], pages: [{ field: 'success' }] }),
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('infiniteQueryOptions', () => {
|
|
32
|
+
it('should be used with useInfiniteQuery', () => {
|
|
33
|
+
doNotExecute(() => {
|
|
34
|
+
expectTypeOf(useInfiniteQuery(infiniteQuery.options())).toEqualTypeOf<
|
|
35
|
+
UseInfiniteQueryResult<{ field: string }>
|
|
36
|
+
>()
|
|
37
|
+
|
|
38
|
+
expectTypeOf(
|
|
39
|
+
useInfiniteQuery({
|
|
40
|
+
...infiniteQuery.options(),
|
|
41
|
+
select: (data) => ({
|
|
42
|
+
pages: data.pages.map(({ field }) => field),
|
|
43
|
+
pageParams: data.pageParams,
|
|
44
|
+
}),
|
|
45
|
+
}),
|
|
46
|
+
).toEqualTypeOf<UseInfiniteQueryResult<string>>()
|
|
47
|
+
|
|
48
|
+
expectTypeOf(
|
|
49
|
+
useInfiniteQuery(infiniteQuery.optionsWithInitialData()),
|
|
50
|
+
).toEqualTypeOf<DefinedUseInfiniteQueryResult<{ field: string }>>()
|
|
51
|
+
|
|
52
|
+
expectTypeOf(
|
|
53
|
+
useInfiniteQuery({
|
|
54
|
+
...infiniteQuery.optionsWithInitialData(),
|
|
55
|
+
select: (data) => ({
|
|
56
|
+
pages: data.pages.map(({ field }) => field),
|
|
57
|
+
pageParams: data.pageParams,
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
).toEqualTypeOf<DefinedUseInfiniteQueryResult<string>>()
|
|
61
|
+
|
|
62
|
+
expectTypeOf(
|
|
63
|
+
useInfiniteQuery({
|
|
64
|
+
queryKey: ['key', 2] as const,
|
|
65
|
+
queryFn: () => Promise.resolve({ field: 'success' }),
|
|
66
|
+
initialData: () => ({
|
|
67
|
+
pages: [{ field: 'success' }],
|
|
68
|
+
pageParams: [],
|
|
69
|
+
}),
|
|
70
|
+
select: (data) => ({
|
|
71
|
+
pages: data.pages.map(({ field }) => field),
|
|
72
|
+
pageParams: data.pageParams,
|
|
73
|
+
}),
|
|
74
|
+
}),
|
|
75
|
+
).toEqualTypeOf<DefinedUseInfiniteQueryResult<string>>()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
it('should be used with useSuspenseInfiniteQuery', () => {
|
|
79
|
+
doNotExecute(() => {
|
|
80
|
+
expectTypeOf(
|
|
81
|
+
useSuspenseInfiniteQuery(infiniteQuery.options()),
|
|
82
|
+
).toEqualTypeOf<UseSuspenseInfiniteQueryResult<{ field: string }>>()
|
|
83
|
+
|
|
84
|
+
expectTypeOf(
|
|
85
|
+
useSuspenseInfiniteQuery({
|
|
86
|
+
...infiniteQuery.options(),
|
|
87
|
+
select: (data) => ({
|
|
88
|
+
pages: data.pages.map(({ field }) => field),
|
|
89
|
+
pageParams: data.pageParams,
|
|
90
|
+
}),
|
|
91
|
+
}),
|
|
92
|
+
).toEqualTypeOf<UseSuspenseInfiniteQueryResult<string>>()
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
it('should be used with useQueryClient', () => {
|
|
96
|
+
doNotExecute(async () => {
|
|
97
|
+
const queryClient = useQueryClient()
|
|
98
|
+
|
|
99
|
+
queryClient.invalidateQueries(infiniteQuery.options())
|
|
100
|
+
queryClient.resetQueries(infiniteQuery.options())
|
|
101
|
+
queryClient.removeQueries(infiniteQuery.options())
|
|
102
|
+
queryClient.cancelQueries(infiniteQuery.options())
|
|
103
|
+
queryClient.prefetchQuery(infiniteQuery.options())
|
|
104
|
+
queryClient.refetchQueries(infiniteQuery.options())
|
|
105
|
+
|
|
106
|
+
expectTypeOf(
|
|
107
|
+
await queryClient.fetchQuery(infiniteQuery.options()),
|
|
108
|
+
).toEqualTypeOf<InfiniteData<{ field: string }>>()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -19,13 +19,14 @@ const queryFn = () => Promise.resolve({ field: 'success' })
|
|
|
19
19
|
describe('queryOptions', () => {
|
|
20
20
|
it('should be used with useQuery', () => {
|
|
21
21
|
doNotExecute(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
expectTypeOf(
|
|
23
|
+
useQuery(
|
|
24
|
+
queryOptions({
|
|
25
|
+
queryKey,
|
|
26
|
+
queryFn,
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
).toEqualTypeOf<UseQueryResult<{ field: string }>>()
|
|
29
30
|
expectTypeOf(
|
|
30
31
|
useQuery({
|
|
31
32
|
...queryOptions({
|
|
@@ -1238,3 +1238,233 @@ describe('useQueries with suspense', () => {
|
|
|
1238
1238
|
expect(results).toEqual(['1', '2', 'loading'])
|
|
1239
1239
|
})
|
|
1240
1240
|
})
|
|
1241
|
+
|
|
1242
|
+
describe('cacheTime minimum enforcement with suspense', () => {
|
|
1243
|
+
const queryClient = createQueryClient()
|
|
1244
|
+
|
|
1245
|
+
it('should not cause infinite re-renders with synchronous query function and cacheTime: 0', async () => {
|
|
1246
|
+
const key = queryKey()
|
|
1247
|
+
let renderCount = 0
|
|
1248
|
+
let queryFnCallCount = 0
|
|
1249
|
+
const maxChecks = 20
|
|
1250
|
+
|
|
1251
|
+
function Page() {
|
|
1252
|
+
renderCount++
|
|
1253
|
+
|
|
1254
|
+
if (renderCount > maxChecks) {
|
|
1255
|
+
throw new Error(`Infinite loop detected! Renders: ${renderCount}`)
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const result = useQuery(
|
|
1259
|
+
key,
|
|
1260
|
+
() => {
|
|
1261
|
+
queryFnCallCount++
|
|
1262
|
+
return 42
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
cacheTime: 0,
|
|
1266
|
+
suspense: true,
|
|
1267
|
+
},
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
return <div>data: {result.data}</div>
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const rendered = renderWithClient(
|
|
1274
|
+
queryClient,
|
|
1275
|
+
<React.Suspense fallback="loading">
|
|
1276
|
+
<Page />
|
|
1277
|
+
</React.Suspense>,
|
|
1278
|
+
)
|
|
1279
|
+
|
|
1280
|
+
await waitFor(() => rendered.getByText('data: 42'))
|
|
1281
|
+
|
|
1282
|
+
expect(renderCount).toBeLessThan(5)
|
|
1283
|
+
expect(queryFnCallCount).toBe(1)
|
|
1284
|
+
expect(rendered.queryByText('data: 42')).not.toBeNull()
|
|
1285
|
+
expect(rendered.queryByText('loading')).toBeNull()
|
|
1286
|
+
})
|
|
1287
|
+
|
|
1288
|
+
describe('boundary value tests', () => {
|
|
1289
|
+
test.each([
|
|
1290
|
+
[0, 1000],
|
|
1291
|
+
[1, 1000],
|
|
1292
|
+
[999, 1000],
|
|
1293
|
+
[1000, 1000],
|
|
1294
|
+
[2000, 2000],
|
|
1295
|
+
])(
|
|
1296
|
+
'cacheTime %i should be adjusted to %i with suspense',
|
|
1297
|
+
async (input, expected) => {
|
|
1298
|
+
const key = queryKey()
|
|
1299
|
+
|
|
1300
|
+
function Page() {
|
|
1301
|
+
const result = useQuery(key, () => 42, {
|
|
1302
|
+
suspense: true,
|
|
1303
|
+
cacheTime: input,
|
|
1304
|
+
})
|
|
1305
|
+
return <div>data: {result.data}</div>
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const rendered = renderWithClient(
|
|
1309
|
+
queryClient,
|
|
1310
|
+
<React.Suspense fallback="loading">
|
|
1311
|
+
<Page />
|
|
1312
|
+
</React.Suspense>,
|
|
1313
|
+
)
|
|
1314
|
+
|
|
1315
|
+
await waitFor(() => rendered.getByText('data: 42'))
|
|
1316
|
+
|
|
1317
|
+
const query = queryClient.getQueryCache().find(key)
|
|
1318
|
+
const options = query?.options
|
|
1319
|
+
expect(options?.cacheTime).toBe(expected)
|
|
1320
|
+
},
|
|
1321
|
+
)
|
|
1322
|
+
})
|
|
1323
|
+
|
|
1324
|
+
it('should preserve user cacheTime when >= 1000ms', async () => {
|
|
1325
|
+
const key = queryKey()
|
|
1326
|
+
const userCacheTime = 5000
|
|
1327
|
+
|
|
1328
|
+
function Page() {
|
|
1329
|
+
useQuery(key, () => 'test', {
|
|
1330
|
+
suspense: true,
|
|
1331
|
+
cacheTime: userCacheTime,
|
|
1332
|
+
})
|
|
1333
|
+
return <div>rendered</div>
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
renderWithClient(
|
|
1337
|
+
queryClient,
|
|
1338
|
+
<React.Suspense fallback="loading">
|
|
1339
|
+
<Page />
|
|
1340
|
+
</React.Suspense>,
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
await waitFor(() => {
|
|
1344
|
+
const query = queryClient.getQueryCache().find(key)
|
|
1345
|
+
const options = query?.options
|
|
1346
|
+
expect(options?.cacheTime).toBe(userCacheTime)
|
|
1347
|
+
})
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
it('should handle async queries with adjusted cacheTime', async () => {
|
|
1351
|
+
const key = queryKey()
|
|
1352
|
+
let renderCount = 0
|
|
1353
|
+
|
|
1354
|
+
function Page() {
|
|
1355
|
+
renderCount++
|
|
1356
|
+
const result = useQuery(
|
|
1357
|
+
key,
|
|
1358
|
+
async () => {
|
|
1359
|
+
await sleep(10)
|
|
1360
|
+
return 'async-result'
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
suspense: true,
|
|
1364
|
+
cacheTime: 0,
|
|
1365
|
+
},
|
|
1366
|
+
)
|
|
1367
|
+
return <div>data: {result.data}</div>
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const rendered = renderWithClient(
|
|
1371
|
+
queryClient,
|
|
1372
|
+
<React.Suspense fallback="loading">
|
|
1373
|
+
<Page />
|
|
1374
|
+
</React.Suspense>,
|
|
1375
|
+
)
|
|
1376
|
+
|
|
1377
|
+
await waitFor(() => rendered.getByText('data: async-result'))
|
|
1378
|
+
expect(renderCount).toBeLessThan(5)
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
describe('staleTime and cacheTime relationship', () => {
|
|
1382
|
+
it('should handle when both need adjustment', async () => {
|
|
1383
|
+
const key = queryKey()
|
|
1384
|
+
|
|
1385
|
+
function Page() {
|
|
1386
|
+
useQuery(key, () => 42, {
|
|
1387
|
+
suspense: true,
|
|
1388
|
+
cacheTime: 0,
|
|
1389
|
+
staleTime: undefined,
|
|
1390
|
+
})
|
|
1391
|
+
return <div>rendered</div>
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
renderWithClient(
|
|
1395
|
+
queryClient,
|
|
1396
|
+
<React.Suspense fallback="loading">
|
|
1397
|
+
<Page />
|
|
1398
|
+
</React.Suspense>,
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
await waitFor(() => {
|
|
1402
|
+
const query = queryClient.getQueryCache().find(key)
|
|
1403
|
+
const options = query?.options as any
|
|
1404
|
+
expect(options?.cacheTime).toBe(1000)
|
|
1405
|
+
expect(options?.staleTime).toBe(1000)
|
|
1406
|
+
})
|
|
1407
|
+
})
|
|
1408
|
+
|
|
1409
|
+
it('should maintain staleTime < cacheTime invariant', async () => {
|
|
1410
|
+
const key = queryKey()
|
|
1411
|
+
|
|
1412
|
+
function Page() {
|
|
1413
|
+
useQuery(key, () => 42, {
|
|
1414
|
+
suspense: true,
|
|
1415
|
+
cacheTime: 500,
|
|
1416
|
+
staleTime: 2000,
|
|
1417
|
+
})
|
|
1418
|
+
return <div>rendered</div>
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
renderWithClient(
|
|
1422
|
+
queryClient,
|
|
1423
|
+
<React.Suspense fallback="loading">
|
|
1424
|
+
<Page />
|
|
1425
|
+
</React.Suspense>,
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
await waitFor(() => {
|
|
1429
|
+
const query = queryClient.getQueryCache().find(key)
|
|
1430
|
+
const options = query?.options as any
|
|
1431
|
+
expect(options?.cacheTime).toBe(1000)
|
|
1432
|
+
expect(options?.staleTime).toBe(2000)
|
|
1433
|
+
})
|
|
1434
|
+
})
|
|
1435
|
+
})
|
|
1436
|
+
|
|
1437
|
+
it('should fix synchronous query with cacheTime 0 infinite loop', async () => {
|
|
1438
|
+
const key = queryKey()
|
|
1439
|
+
let renderCount = 0
|
|
1440
|
+
let queryFnCallCount = 0
|
|
1441
|
+
|
|
1442
|
+
function Page() {
|
|
1443
|
+
renderCount++
|
|
1444
|
+
const result = useQuery(
|
|
1445
|
+
key,
|
|
1446
|
+
() => {
|
|
1447
|
+
queryFnCallCount++
|
|
1448
|
+
return 42
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
suspense: true,
|
|
1452
|
+
cacheTime: 0,
|
|
1453
|
+
},
|
|
1454
|
+
)
|
|
1455
|
+
return <div>data: {result.data}</div>
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const rendered = renderWithClient(
|
|
1459
|
+
queryClient,
|
|
1460
|
+
<React.Suspense fallback="loading">
|
|
1461
|
+
<Page />
|
|
1462
|
+
</React.Suspense>,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1465
|
+
await waitFor(() => rendered.getByText('data: 42'))
|
|
1466
|
+
|
|
1467
|
+
expect(renderCount).toBeLessThan(5)
|
|
1468
|
+
expect(queryFnCallCount).toBe(1)
|
|
1469
|
+
})
|
|
1470
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { expectTypeOf } from 'expect-type'
|
|
2
|
+
import { infiniteQueryOptions, useSuspenseInfiniteQuery } from '..'
|
|
3
|
+
import { doNotExecute, sleep } from './utils'
|
|
4
|
+
import type { UseSuspenseInfiniteQueryResult } from '..'
|
|
5
|
+
|
|
6
|
+
import type { InfiniteData } from '@tanstack/react-query'
|
|
7
|
+
|
|
8
|
+
const queryKey = ['key'] as const
|
|
9
|
+
const queryFn = () => sleep(10).then(() => ({ text: 'response' }))
|
|
10
|
+
|
|
11
|
+
describe('useSuspenseInfiniteQuery', () => {
|
|
12
|
+
it('type check', () => {
|
|
13
|
+
doNotExecute(() => {
|
|
14
|
+
// @ts-expect-error no arg
|
|
15
|
+
useSuspenseInfiniteQuery()
|
|
16
|
+
|
|
17
|
+
useSuspenseInfiniteQuery({
|
|
18
|
+
queryKey,
|
|
19
|
+
queryFn,
|
|
20
|
+
// @ts-expect-error no suspense
|
|
21
|
+
suspense: boolean,
|
|
22
|
+
})
|
|
23
|
+
useSuspenseInfiniteQuery({
|
|
24
|
+
queryKey,
|
|
25
|
+
queryFn,
|
|
26
|
+
// @ts-expect-error no useErrorBoundary
|
|
27
|
+
useErrorBoundary: boolean,
|
|
28
|
+
})
|
|
29
|
+
useSuspenseInfiniteQuery({
|
|
30
|
+
queryKey,
|
|
31
|
+
queryFn,
|
|
32
|
+
// @ts-expect-error no enabled
|
|
33
|
+
enabled: boolean,
|
|
34
|
+
})
|
|
35
|
+
useSuspenseInfiniteQuery({
|
|
36
|
+
queryKey,
|
|
37
|
+
queryFn,
|
|
38
|
+
// @ts-expect-error no placeholderData
|
|
39
|
+
placeholderData: 'placeholder',
|
|
40
|
+
})
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
42
|
+
useSuspenseInfiniteQuery({
|
|
43
|
+
queryKey,
|
|
44
|
+
queryFn,
|
|
45
|
+
// @ts-expect-error no isPlaceholderData
|
|
46
|
+
}).isPlaceholderData
|
|
47
|
+
useSuspenseInfiniteQuery({
|
|
48
|
+
queryKey,
|
|
49
|
+
queryFn,
|
|
50
|
+
//@ts-expect-error no networkMode
|
|
51
|
+
networkMode: 'always',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const infiniteQuery = useSuspenseInfiniteQuery({ queryKey, queryFn })
|
|
55
|
+
expectTypeOf(infiniteQuery).toEqualTypeOf<
|
|
56
|
+
UseSuspenseInfiniteQueryResult<{ text: string }>
|
|
57
|
+
>()
|
|
58
|
+
expectTypeOf(infiniteQuery.data).toEqualTypeOf<
|
|
59
|
+
InfiniteData<{ text: string }>
|
|
60
|
+
>()
|
|
61
|
+
expectTypeOf(infiniteQuery.status).toEqualTypeOf<'error' | 'success'>()
|
|
62
|
+
|
|
63
|
+
const selectedInfiniteQuery = useSuspenseInfiniteQuery({
|
|
64
|
+
queryKey,
|
|
65
|
+
queryFn,
|
|
66
|
+
select: (data) => ({
|
|
67
|
+
pages: data.pages.map(({ text }) => text),
|
|
68
|
+
pageParams: data.pageParams,
|
|
69
|
+
}),
|
|
70
|
+
})
|
|
71
|
+
expectTypeOf(selectedInfiniteQuery).toEqualTypeOf<
|
|
72
|
+
UseSuspenseInfiniteQueryResult<string>
|
|
73
|
+
>()
|
|
74
|
+
expectTypeOf(selectedInfiniteQuery.data).toEqualTypeOf<
|
|
75
|
+
InfiniteData<string>
|
|
76
|
+
>()
|
|
77
|
+
expectTypeOf(selectedInfiniteQuery.status).toEqualTypeOf<
|
|
78
|
+
'error' | 'success'
|
|
79
|
+
>()
|
|
80
|
+
|
|
81
|
+
const options = infiniteQueryOptions({
|
|
82
|
+
queryKey,
|
|
83
|
+
queryFn,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const infiniteQueryWithOptions = useSuspenseInfiniteQuery(options)
|
|
87
|
+
expectTypeOf(infiniteQueryWithOptions).toEqualTypeOf<
|
|
88
|
+
UseSuspenseInfiniteQueryResult<{ text: string }>
|
|
89
|
+
>()
|
|
90
|
+
expectTypeOf(infiniteQueryWithOptions.data).toEqualTypeOf<
|
|
91
|
+
InfiniteData<{ text: string }>
|
|
92
|
+
>()
|
|
93
|
+
expectTypeOf(infiniteQueryWithOptions.status).toEqualTypeOf<
|
|
94
|
+
'error' | 'success'
|
|
95
|
+
>()
|
|
96
|
+
|
|
97
|
+
const selectedInfiniteQueryWithOptions = useSuspenseInfiniteQuery({
|
|
98
|
+
...options,
|
|
99
|
+
select: (data) => ({
|
|
100
|
+
pages: data.pages.map(({ text }) => text),
|
|
101
|
+
pageParams: data.pageParams,
|
|
102
|
+
}),
|
|
103
|
+
})
|
|
104
|
+
expectTypeOf(selectedInfiniteQueryWithOptions).toEqualTypeOf<
|
|
105
|
+
UseSuspenseInfiniteQueryResult<string>
|
|
106
|
+
>()
|
|
107
|
+
expectTypeOf(selectedInfiniteQueryWithOptions.data).toEqualTypeOf<
|
|
108
|
+
InfiniteData<string>
|
|
109
|
+
>()
|
|
110
|
+
expectTypeOf(selectedInfiniteQueryWithOptions.status).toEqualTypeOf<
|
|
111
|
+
'error' | 'success'
|
|
112
|
+
>()
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { useQueries } from './useQueries'
|
|
|
12
12
|
export type { QueriesResults, QueriesOptions } from './useQueries'
|
|
13
13
|
export { useQuery } from './useQuery'
|
|
14
14
|
export { useSuspenseQuery } from './useSuspenseQuery'
|
|
15
|
+
export { useSuspenseInfiniteQuery } from './useSuspenseInfiniteQuery'
|
|
15
16
|
export { useSuspenseQueries } from './useSuspenseQueries'
|
|
16
17
|
export type {
|
|
17
18
|
SuspenseQueriesResults,
|
|
@@ -22,6 +23,11 @@ export type {
|
|
|
22
23
|
DefinedInitialDataOptions,
|
|
23
24
|
UndefinedInitialDataOptions,
|
|
24
25
|
} from './queryOptions'
|
|
26
|
+
export { infiniteQueryOptions } from './infiniteQueryOptions'
|
|
27
|
+
export type {
|
|
28
|
+
DefinedInitialDataInfiniteOptions,
|
|
29
|
+
UndefinedInitialDataInfiniteOptions,
|
|
30
|
+
} from './infiniteQueryOptions'
|
|
25
31
|
export {
|
|
26
32
|
defaultContext,
|
|
27
33
|
QueryClientProvider,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { UseInfiniteQueryOptions } from './types'
|
|
2
|
+
import type {
|
|
3
|
+
InfiniteData,
|
|
4
|
+
NonUndefinedGuard,
|
|
5
|
+
OmitKeyof,
|
|
6
|
+
QueryKey,
|
|
7
|
+
WithRequired,
|
|
8
|
+
} from '@tanstack/query-core'
|
|
9
|
+
|
|
10
|
+
type UseInfiniteQueryOptionsOmitted<
|
|
11
|
+
TQueryFnData = unknown,
|
|
12
|
+
TError = unknown,
|
|
13
|
+
TData = TQueryFnData,
|
|
14
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
15
|
+
> = OmitKeyof<
|
|
16
|
+
UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
|
|
17
|
+
'onSuccess' | 'onError' | 'onSettled' | 'refetchInterval'
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
type ProhibitedInfiniteQueryOptionsKeyInV5 = keyof Pick<
|
|
21
|
+
UseInfiniteQueryOptionsOmitted,
|
|
22
|
+
'useErrorBoundary' | 'suspense'
|
|
23
|
+
>
|
|
24
|
+
|
|
25
|
+
export type UndefinedInitialDataInfiniteOptions<
|
|
26
|
+
TQueryFnData,
|
|
27
|
+
TError = unknown,
|
|
28
|
+
TData = TQueryFnData,
|
|
29
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
30
|
+
> = UseInfiniteQueryOptionsOmitted<TQueryFnData, TError, TData, TQueryKey> & {
|
|
31
|
+
initialData?: undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type DefinedInitialDataInfiniteOptions<
|
|
35
|
+
TQueryFnData,
|
|
36
|
+
TError = unknown,
|
|
37
|
+
TData = TQueryFnData,
|
|
38
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
39
|
+
> = UseInfiniteQueryOptionsOmitted<TQueryFnData, TError, TData, TQueryKey> & {
|
|
40
|
+
initialData:
|
|
41
|
+
| NonUndefinedGuard<InfiniteData<TQueryFnData>>
|
|
42
|
+
| (() => NonUndefinedGuard<InfiniteData<TQueryFnData>>)
|
|
43
|
+
| undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function infiniteQueryOptions<
|
|
47
|
+
TQueryFnData,
|
|
48
|
+
TError = unknown,
|
|
49
|
+
TData = TQueryFnData,
|
|
50
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
51
|
+
>(
|
|
52
|
+
options: WithRequired<
|
|
53
|
+
OmitKeyof<
|
|
54
|
+
DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
55
|
+
ProhibitedInfiniteQueryOptionsKeyInV5
|
|
56
|
+
>,
|
|
57
|
+
'queryKey'
|
|
58
|
+
>,
|
|
59
|
+
): WithRequired<
|
|
60
|
+
OmitKeyof<
|
|
61
|
+
DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
62
|
+
ProhibitedInfiniteQueryOptionsKeyInV5
|
|
63
|
+
>,
|
|
64
|
+
'queryKey'
|
|
65
|
+
>
|
|
66
|
+
|
|
67
|
+
export function infiniteQueryOptions<
|
|
68
|
+
TQueryFnData,
|
|
69
|
+
TError = unknown,
|
|
70
|
+
TData = TQueryFnData,
|
|
71
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
72
|
+
>(
|
|
73
|
+
options: WithRequired<
|
|
74
|
+
OmitKeyof<
|
|
75
|
+
UndefinedInitialDataInfiniteOptions<
|
|
76
|
+
TQueryFnData,
|
|
77
|
+
TError,
|
|
78
|
+
TData,
|
|
79
|
+
TQueryKey
|
|
80
|
+
>,
|
|
81
|
+
ProhibitedInfiniteQueryOptionsKeyInV5
|
|
82
|
+
>,
|
|
83
|
+
'queryKey'
|
|
84
|
+
>,
|
|
85
|
+
): WithRequired<
|
|
86
|
+
OmitKeyof<
|
|
87
|
+
UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
88
|
+
ProhibitedInfiniteQueryOptionsKeyInV5
|
|
89
|
+
>,
|
|
90
|
+
'queryKey'
|
|
91
|
+
>
|
|
92
|
+
|
|
93
|
+
export function infiniteQueryOptions(options: unknown) {
|
|
94
|
+
return options
|
|
95
|
+
}
|
package/src/queryOptions.ts
CHANGED
|
@@ -59,7 +59,10 @@ export function queryOptions<
|
|
|
59
59
|
'queryKey'
|
|
60
60
|
>,
|
|
61
61
|
): WithRequired<
|
|
62
|
-
|
|
62
|
+
OmitKeyof<
|
|
63
|
+
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
64
|
+
ProhibitedQueryOptionsKeyInV5
|
|
65
|
+
>,
|
|
63
66
|
'queryKey'
|
|
64
67
|
>
|
|
65
68
|
|
|
@@ -77,7 +80,10 @@ export function queryOptions<
|
|
|
77
80
|
'queryKey'
|
|
78
81
|
>,
|
|
79
82
|
): WithRequired<
|
|
80
|
-
|
|
83
|
+
OmitKeyof<
|
|
84
|
+
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
85
|
+
ProhibitedQueryOptionsKeyInV5
|
|
86
|
+
>,
|
|
81
87
|
'queryKey'
|
|
82
88
|
>
|
|
83
89
|
|
package/src/suspense.ts
CHANGED
|
@@ -4,6 +4,13 @@ import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
|
|
|
4
4
|
import type { QueryObserverResult } from '@tanstack/query-core'
|
|
5
5
|
import type { QueryKey } from '@tanstack/query-core'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Ensures minimum staleTime and cacheTime values when suspense is enabled.
|
|
9
|
+
* Despite the name, this function guards both staleTime and cacheTime to prevent
|
|
10
|
+
* infinite re-render loops with synchronous queries.
|
|
11
|
+
*
|
|
12
|
+
* @deprecated in v5 - replaced by ensureSuspenseTimers
|
|
13
|
+
*/
|
|
7
14
|
export const ensureStaleTime = (
|
|
8
15
|
defaultedOptions: DefaultedQueryObserverOptions<any, any, any, any, any>,
|
|
9
16
|
) => {
|
|
@@ -13,6 +20,10 @@ export const ensureStaleTime = (
|
|
|
13
20
|
if (typeof defaultedOptions.staleTime !== 'number') {
|
|
14
21
|
defaultedOptions.staleTime = 1000
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
if (typeof defaultedOptions.cacheTime === 'number') {
|
|
25
|
+
defaultedOptions.cacheTime = Math.max(defaultedOptions.cacheTime, 1000)
|
|
26
|
+
}
|
|
16
27
|
}
|
|
17
28
|
}
|
|
18
29
|
|