@tanstack/query-core 5.56.2 → 5.59.4
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/{hydration-D6canbuP.d.cts → hydration-CVKA21uy.d.cts} +53 -0
- package/build/legacy/{hydration-DgUcX5FN.d.ts → hydration-DHZNyHqg.d.ts} +53 -0
- package/build/legacy/hydration.d.cts +1 -1
- package/build/legacy/hydration.d.ts +1 -1
- package/build/legacy/index.d.cts +1 -1
- package/build/legacy/index.d.ts +1 -1
- package/build/legacy/infiniteQueryBehavior.d.cts +1 -1
- package/build/legacy/infiniteQueryBehavior.d.ts +1 -1
- package/build/legacy/infiniteQueryObserver.d.cts +1 -1
- package/build/legacy/infiniteQueryObserver.d.ts +1 -1
- package/build/legacy/mutation.d.cts +1 -1
- package/build/legacy/mutation.d.ts +1 -1
- package/build/legacy/mutationCache.d.cts +1 -1
- package/build/legacy/mutationCache.d.ts +1 -1
- package/build/legacy/mutationObserver.d.cts +1 -1
- package/build/legacy/mutationObserver.d.ts +1 -1
- package/build/legacy/queriesObserver.cjs +19 -8
- package/build/legacy/queriesObserver.cjs.map +1 -1
- package/build/legacy/queriesObserver.d.cts +3 -3
- package/build/legacy/queriesObserver.d.ts +3 -3
- package/build/legacy/queriesObserver.js +19 -8
- package/build/legacy/queriesObserver.js.map +1 -1
- package/build/legacy/query.d.cts +1 -1
- package/build/legacy/query.d.ts +1 -1
- package/build/legacy/queryCache.d.cts +1 -1
- package/build/legacy/queryCache.d.ts +1 -1
- package/build/legacy/queryClient.d.cts +1 -1
- package/build/legacy/queryClient.d.ts +1 -1
- package/build/legacy/queryObserver.cjs +44 -5
- package/build/legacy/queryObserver.cjs.map +1 -1
- package/build/legacy/queryObserver.d.cts +1 -1
- package/build/legacy/queryObserver.d.ts +1 -1
- package/build/legacy/queryObserver.js +45 -6
- package/build/legacy/queryObserver.js.map +1 -1
- package/build/legacy/retryer.cjs +7 -11
- package/build/legacy/retryer.cjs.map +1 -1
- package/build/legacy/retryer.d.cts +1 -1
- package/build/legacy/retryer.d.ts +1 -1
- package/build/legacy/retryer.js +7 -11
- package/build/legacy/retryer.js.map +1 -1
- package/build/legacy/thenable.cjs +61 -0
- package/build/legacy/thenable.cjs.map +1 -0
- package/build/legacy/thenable.d.cts +36 -0
- package/build/legacy/thenable.d.ts +36 -0
- package/build/legacy/thenable.js +38 -0
- package/build/legacy/thenable.js.map +1 -0
- package/build/legacy/types.cjs.map +1 -1
- package/build/legacy/types.d.cts +1 -1
- package/build/legacy/types.d.ts +1 -1
- package/build/legacy/utils.d.cts +1 -1
- package/build/legacy/utils.d.ts +1 -1
- package/build/modern/{hydration-D6canbuP.d.cts → hydration-CVKA21uy.d.cts} +53 -0
- package/build/modern/{hydration-DgUcX5FN.d.ts → hydration-DHZNyHqg.d.ts} +53 -0
- package/build/modern/hydration.d.cts +1 -1
- package/build/modern/hydration.d.ts +1 -1
- package/build/modern/index.d.cts +1 -1
- package/build/modern/index.d.ts +1 -1
- package/build/modern/infiniteQueryBehavior.d.cts +1 -1
- package/build/modern/infiniteQueryBehavior.d.ts +1 -1
- package/build/modern/infiniteQueryObserver.d.cts +1 -1
- package/build/modern/infiniteQueryObserver.d.ts +1 -1
- package/build/modern/mutation.d.cts +1 -1
- package/build/modern/mutation.d.ts +1 -1
- package/build/modern/mutationCache.d.cts +1 -1
- package/build/modern/mutationCache.d.ts +1 -1
- package/build/modern/mutationObserver.d.cts +1 -1
- package/build/modern/mutationObserver.d.ts +1 -1
- package/build/modern/queriesObserver.cjs +19 -7
- package/build/modern/queriesObserver.cjs.map +1 -1
- package/build/modern/queriesObserver.d.cts +3 -3
- package/build/modern/queriesObserver.d.ts +3 -3
- package/build/modern/queriesObserver.js +19 -7
- package/build/modern/queriesObserver.js.map +1 -1
- package/build/modern/query.d.cts +1 -1
- package/build/modern/query.d.ts +1 -1
- package/build/modern/queryCache.d.cts +1 -1
- package/build/modern/queryCache.d.ts +1 -1
- package/build/modern/queryClient.d.cts +1 -1
- package/build/modern/queryClient.d.ts +1 -1
- package/build/modern/queryObserver.cjs +42 -4
- package/build/modern/queryObserver.cjs.map +1 -1
- package/build/modern/queryObserver.d.cts +1 -1
- package/build/modern/queryObserver.d.ts +1 -1
- package/build/modern/queryObserver.js +43 -5
- package/build/modern/queryObserver.js.map +1 -1
- package/build/modern/retryer.cjs +7 -11
- package/build/modern/retryer.cjs.map +1 -1
- package/build/modern/retryer.d.cts +1 -1
- package/build/modern/retryer.d.ts +1 -1
- package/build/modern/retryer.js +7 -11
- package/build/modern/retryer.js.map +1 -1
- package/build/modern/thenable.cjs +61 -0
- package/build/modern/thenable.cjs.map +1 -0
- package/build/modern/thenable.d.cts +36 -0
- package/build/modern/thenable.d.ts +36 -0
- package/build/modern/thenable.js +36 -0
- package/build/modern/thenable.js.map +1 -0
- package/build/modern/types.cjs.map +1 -1
- package/build/modern/types.d.cts +1 -1
- package/build/modern/types.d.ts +1 -1
- package/build/modern/utils.d.cts +1 -1
- package/build/modern/utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/hydration.test.tsx +38 -0
- package/src/__tests__/queryObserver.test.tsx +101 -1
- package/src/queriesObserver.ts +20 -7
- package/src/queryObserver.ts +63 -4
- package/src/retryer.ts +7 -11
- package/src/thenable.ts +82 -0
- package/src/types.ts +54 -0
|
@@ -16,7 +16,13 @@ describe('queryObserver', () => {
|
|
|
16
16
|
let queryClient: QueryClient
|
|
17
17
|
|
|
18
18
|
beforeEach(() => {
|
|
19
|
-
queryClient = createQueryClient(
|
|
19
|
+
queryClient = createQueryClient({
|
|
20
|
+
defaultOptions: {
|
|
21
|
+
queries: {
|
|
22
|
+
experimental_prefetchInRender: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
})
|
|
20
26
|
queryClient.mount()
|
|
21
27
|
})
|
|
22
28
|
|
|
@@ -1133,4 +1139,98 @@ describe('queryObserver', () => {
|
|
|
1133
1139
|
|
|
1134
1140
|
unsubscribe()
|
|
1135
1141
|
})
|
|
1142
|
+
|
|
1143
|
+
test('should return a promise that resolves when data is present', async () => {
|
|
1144
|
+
const results: Array<QueryObserverResult> = []
|
|
1145
|
+
const key = queryKey()
|
|
1146
|
+
let count = 0
|
|
1147
|
+
const observer = new QueryObserver(queryClient, {
|
|
1148
|
+
queryKey: key,
|
|
1149
|
+
queryFn: () => {
|
|
1150
|
+
if (++count > 9) {
|
|
1151
|
+
return Promise.resolve('data')
|
|
1152
|
+
}
|
|
1153
|
+
throw new Error('rejected')
|
|
1154
|
+
},
|
|
1155
|
+
retry: 10,
|
|
1156
|
+
retryDelay: 0,
|
|
1157
|
+
})
|
|
1158
|
+
const unsubscribe = observer.subscribe(() => {
|
|
1159
|
+
results.push(observer.getCurrentResult())
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
await waitFor(() => {
|
|
1163
|
+
expect(results.at(-1)?.data).toBe('data')
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
const numberOfUniquePromises = new Set(
|
|
1167
|
+
results.map((result) => result.promise),
|
|
1168
|
+
).size
|
|
1169
|
+
expect(numberOfUniquePromises).toBe(1)
|
|
1170
|
+
|
|
1171
|
+
unsubscribe()
|
|
1172
|
+
})
|
|
1173
|
+
|
|
1174
|
+
test('should return a new promise after recovering from an error', async () => {
|
|
1175
|
+
const results: Array<QueryObserverResult> = []
|
|
1176
|
+
const key = queryKey()
|
|
1177
|
+
|
|
1178
|
+
let succeeds = false
|
|
1179
|
+
let idx = 0
|
|
1180
|
+
const observer = new QueryObserver(queryClient, {
|
|
1181
|
+
queryKey: key,
|
|
1182
|
+
queryFn: () => {
|
|
1183
|
+
if (succeeds) {
|
|
1184
|
+
return Promise.resolve('data')
|
|
1185
|
+
}
|
|
1186
|
+
throw new Error(`rejected #${++idx}`)
|
|
1187
|
+
},
|
|
1188
|
+
retry: 5,
|
|
1189
|
+
retryDelay: 0,
|
|
1190
|
+
})
|
|
1191
|
+
const unsubscribe = observer.subscribe(() => {
|
|
1192
|
+
results.push(observer.getCurrentResult())
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
await waitFor(() => {
|
|
1196
|
+
expect(results.at(-1)?.status).toBe('error')
|
|
1197
|
+
})
|
|
1198
|
+
|
|
1199
|
+
expect(
|
|
1200
|
+
results.every((result) => result.promise === results[0]!.promise),
|
|
1201
|
+
).toBe(true)
|
|
1202
|
+
|
|
1203
|
+
{
|
|
1204
|
+
// fail again
|
|
1205
|
+
const lengthBefore = results.length
|
|
1206
|
+
observer.refetch()
|
|
1207
|
+
await waitFor(() => {
|
|
1208
|
+
expect(results.length).toBeGreaterThan(lengthBefore)
|
|
1209
|
+
expect(results.at(-1)?.status).toBe('error')
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
const numberOfUniquePromises = new Set(
|
|
1213
|
+
results.map((result) => result.promise),
|
|
1214
|
+
).size
|
|
1215
|
+
|
|
1216
|
+
expect(numberOfUniquePromises).toBe(2)
|
|
1217
|
+
}
|
|
1218
|
+
{
|
|
1219
|
+
// succeed
|
|
1220
|
+
succeeds = true
|
|
1221
|
+
observer.refetch()
|
|
1222
|
+
|
|
1223
|
+
await waitFor(() => {
|
|
1224
|
+
results.at(-1)?.status === 'success'
|
|
1225
|
+
})
|
|
1226
|
+
|
|
1227
|
+
const numberOfUniquePromises = new Set(
|
|
1228
|
+
results.map((result) => result.promise),
|
|
1229
|
+
).size
|
|
1230
|
+
|
|
1231
|
+
expect(numberOfUniquePromises).toBe(3)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
unsubscribe()
|
|
1235
|
+
})
|
|
1136
1236
|
})
|
package/src/queriesObserver.ts
CHANGED
|
@@ -38,6 +38,7 @@ export class QueriesObserver<
|
|
|
38
38
|
#client: QueryClient
|
|
39
39
|
#result!: Array<QueryObserverResult>
|
|
40
40
|
#queries: Array<QueryObserverOptions>
|
|
41
|
+
#options?: QueriesObserverOptions<TCombinedResult>
|
|
41
42
|
#observers: Array<QueryObserver>
|
|
42
43
|
#combinedResult?: TCombinedResult
|
|
43
44
|
#lastCombine?: CombineFn<TCombinedResult>
|
|
@@ -46,11 +47,12 @@ export class QueriesObserver<
|
|
|
46
47
|
constructor(
|
|
47
48
|
client: QueryClient,
|
|
48
49
|
queries: Array<QueryObserverOptions<any, any, any, any, any>>,
|
|
49
|
-
|
|
50
|
+
options?: QueriesObserverOptions<TCombinedResult>,
|
|
50
51
|
) {
|
|
51
52
|
super()
|
|
52
53
|
|
|
53
54
|
this.#client = client
|
|
55
|
+
this.#options = options
|
|
54
56
|
this.#queries = []
|
|
55
57
|
this.#observers = []
|
|
56
58
|
this.#result = []
|
|
@@ -83,10 +85,11 @@ export class QueriesObserver<
|
|
|
83
85
|
|
|
84
86
|
setQueries(
|
|
85
87
|
queries: Array<QueryObserverOptions>,
|
|
86
|
-
|
|
88
|
+
options?: QueriesObserverOptions<TCombinedResult>,
|
|
87
89
|
notifyOptions?: NotifyOptions,
|
|
88
90
|
): void {
|
|
89
91
|
this.#queries = queries
|
|
92
|
+
this.#options = options
|
|
90
93
|
|
|
91
94
|
notifyManager.batch(() => {
|
|
92
95
|
const prevObservers = this.#observers
|
|
@@ -268,11 +271,21 @@ export class QueriesObserver<
|
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
#notify(): void {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
if (this.hasListeners()) {
|
|
275
|
+
const previousResult = this.#combinedResult
|
|
276
|
+
const newResult = this.#combineResult(
|
|
277
|
+
this.#result,
|
|
278
|
+
this.#options?.combine,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if (previousResult !== newResult) {
|
|
282
|
+
notifyManager.batch(() => {
|
|
283
|
+
this.listeners.forEach((listener) => {
|
|
284
|
+
listener(this.#result)
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
}
|
|
276
289
|
}
|
|
277
290
|
}
|
|
278
291
|
|
package/src/queryObserver.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { focusManager } from './focusManager'
|
|
2
|
+
import { notifyManager } from './notifyManager'
|
|
3
|
+
import { fetchState } from './query'
|
|
4
|
+
import { Subscribable } from './subscribable'
|
|
5
|
+
import { pendingThenable } from './thenable'
|
|
1
6
|
import {
|
|
2
7
|
isServer,
|
|
3
8
|
isValidTimeout,
|
|
@@ -8,12 +13,9 @@ import {
|
|
|
8
13
|
shallowEqualObjects,
|
|
9
14
|
timeUntilStale,
|
|
10
15
|
} from './utils'
|
|
11
|
-
import { notifyManager } from './notifyManager'
|
|
12
|
-
import { focusManager } from './focusManager'
|
|
13
|
-
import { Subscribable } from './subscribable'
|
|
14
|
-
import { fetchState } from './query'
|
|
15
16
|
import type { FetchOptions, Query, QueryState } from './query'
|
|
16
17
|
import type { QueryClient } from './queryClient'
|
|
18
|
+
import type { PendingThenable, Thenable } from './thenable'
|
|
17
19
|
import type {
|
|
18
20
|
DefaultError,
|
|
19
21
|
DefaultedQueryObserverOptions,
|
|
@@ -57,6 +59,7 @@ export class QueryObserver<
|
|
|
57
59
|
TQueryData,
|
|
58
60
|
TQueryKey
|
|
59
61
|
>
|
|
62
|
+
#currentThenable: Thenable<TData>
|
|
60
63
|
#selectError: TError | null
|
|
61
64
|
#selectFn?: (data: TQueryData) => TData
|
|
62
65
|
#selectResult?: TData
|
|
@@ -82,6 +85,13 @@ export class QueryObserver<
|
|
|
82
85
|
|
|
83
86
|
this.#client = client
|
|
84
87
|
this.#selectError = null
|
|
88
|
+
this.#currentThenable = pendingThenable()
|
|
89
|
+
if (!this.options.experimental_prefetchInRender) {
|
|
90
|
+
this.#currentThenable.reject(
|
|
91
|
+
new Error('experimental_prefetchInRender feature flag is not enabled'),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
85
95
|
this.bindMethods()
|
|
86
96
|
this.setOptions(options)
|
|
87
97
|
}
|
|
@@ -582,6 +592,7 @@ export class QueryObserver<
|
|
|
582
592
|
isRefetchError: isError && hasData,
|
|
583
593
|
isStale: isStale(query, options),
|
|
584
594
|
refetch: this.refetch,
|
|
595
|
+
promise: this.#currentThenable,
|
|
585
596
|
}
|
|
586
597
|
|
|
587
598
|
return result as QueryObserverResult<TData, TError>
|
|
@@ -593,6 +604,7 @@ export class QueryObserver<
|
|
|
593
604
|
| undefined
|
|
594
605
|
|
|
595
606
|
const nextResult = this.createResult(this.#currentQuery, this.options)
|
|
607
|
+
|
|
596
608
|
this.#currentResultState = this.#currentQuery.state
|
|
597
609
|
this.#currentResultOptions = this.options
|
|
598
610
|
|
|
@@ -605,6 +617,52 @@ export class QueryObserver<
|
|
|
605
617
|
return
|
|
606
618
|
}
|
|
607
619
|
|
|
620
|
+
if (this.options.experimental_prefetchInRender) {
|
|
621
|
+
const finalizeThenableIfPossible = (thenable: PendingThenable<TData>) => {
|
|
622
|
+
if (nextResult.status === 'error') {
|
|
623
|
+
thenable.reject(nextResult.error)
|
|
624
|
+
} else if (nextResult.data !== undefined) {
|
|
625
|
+
thenable.resolve(nextResult.data)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Create a new thenable and result promise when the results have changed
|
|
631
|
+
*/
|
|
632
|
+
const recreateThenable = () => {
|
|
633
|
+
const pending =
|
|
634
|
+
(this.#currentThenable =
|
|
635
|
+
nextResult.promise =
|
|
636
|
+
pendingThenable())
|
|
637
|
+
|
|
638
|
+
finalizeThenableIfPossible(pending)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const prevThenable = this.#currentThenable
|
|
642
|
+
switch (prevThenable.status) {
|
|
643
|
+
case 'pending':
|
|
644
|
+
// Finalize the previous thenable if it was pending
|
|
645
|
+
finalizeThenableIfPossible(prevThenable)
|
|
646
|
+
break
|
|
647
|
+
case 'fulfilled':
|
|
648
|
+
if (
|
|
649
|
+
nextResult.status === 'error' ||
|
|
650
|
+
nextResult.data !== prevThenable.value
|
|
651
|
+
) {
|
|
652
|
+
recreateThenable()
|
|
653
|
+
}
|
|
654
|
+
break
|
|
655
|
+
case 'rejected':
|
|
656
|
+
if (
|
|
657
|
+
nextResult.status !== 'error' ||
|
|
658
|
+
nextResult.error !== prevThenable.reason
|
|
659
|
+
) {
|
|
660
|
+
recreateThenable()
|
|
661
|
+
}
|
|
662
|
+
break
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
608
666
|
this.#currentResult = nextResult
|
|
609
667
|
|
|
610
668
|
// Determine which callbacks to trigger
|
|
@@ -639,6 +697,7 @@ export class QueryObserver<
|
|
|
639
697
|
return Object.keys(this.#currentResult).some((key) => {
|
|
640
698
|
const typedKey = key as keyof QueryObserverResult
|
|
641
699
|
const changed = this.#currentResult[typedKey] !== prevResult[typedKey]
|
|
700
|
+
|
|
642
701
|
return changed && includedProps.has(typedKey)
|
|
643
702
|
})
|
|
644
703
|
}
|
package/src/retryer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { focusManager } from './focusManager'
|
|
2
2
|
import { onlineManager } from './onlineManager'
|
|
3
|
+
import { pendingThenable } from './thenable'
|
|
3
4
|
import { isServer, sleep } from './utils'
|
|
4
5
|
import type { CancelOptions, DefaultError, NetworkMode } from './types'
|
|
5
6
|
|
|
@@ -75,13 +76,8 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
75
76
|
let failureCount = 0
|
|
76
77
|
let isResolved = false
|
|
77
78
|
let continueFn: ((value?: unknown) => void) | undefined
|
|
78
|
-
let promiseResolve: (data: TData) => void
|
|
79
|
-
let promiseReject: (error: TError) => void
|
|
80
79
|
|
|
81
|
-
const
|
|
82
|
-
promiseResolve = outerResolve
|
|
83
|
-
promiseReject = outerReject
|
|
84
|
-
})
|
|
80
|
+
const thenable = pendingThenable<TData>()
|
|
85
81
|
|
|
86
82
|
const cancel = (cancelOptions?: CancelOptions): void => {
|
|
87
83
|
if (!isResolved) {
|
|
@@ -110,7 +106,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
110
106
|
isResolved = true
|
|
111
107
|
config.onSuccess?.(value)
|
|
112
108
|
continueFn?.()
|
|
113
|
-
|
|
109
|
+
thenable.resolve(value)
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
|
|
@@ -119,7 +115,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
119
115
|
isResolved = true
|
|
120
116
|
config.onError?.(value)
|
|
121
117
|
continueFn?.()
|
|
122
|
-
|
|
118
|
+
thenable.reject(value)
|
|
123
119
|
}
|
|
124
120
|
}
|
|
125
121
|
|
|
@@ -207,11 +203,11 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
207
203
|
}
|
|
208
204
|
|
|
209
205
|
return {
|
|
210
|
-
promise,
|
|
206
|
+
promise: thenable,
|
|
211
207
|
cancel,
|
|
212
208
|
continue: () => {
|
|
213
209
|
continueFn?.()
|
|
214
|
-
return
|
|
210
|
+
return thenable
|
|
215
211
|
},
|
|
216
212
|
cancelRetry,
|
|
217
213
|
continueRetry,
|
|
@@ -223,7 +219,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
|
|
|
223
219
|
} else {
|
|
224
220
|
pause().then(run)
|
|
225
221
|
}
|
|
226
|
-
return
|
|
222
|
+
return thenable
|
|
227
223
|
},
|
|
228
224
|
}
|
|
229
225
|
}
|
package/src/thenable.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thenable types which matches React's types for promises
|
|
3
|
+
*
|
|
4
|
+
* React seemingly uses `.status`, `.value` and `.reason` properties on a promises to optimistically unwrap data from promises
|
|
5
|
+
*
|
|
6
|
+
* @see https://github.com/facebook/react/blob/main/packages/shared/ReactTypes.js#L112-L138
|
|
7
|
+
* @see https://github.com/facebook/react/blob/4f604941569d2e8947ce1460a0b2997e835f37b9/packages/react-debug-tools/src/ReactDebugHooks.js#L224-L227
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface Fulfilled<T> {
|
|
11
|
+
status: 'fulfilled'
|
|
12
|
+
value: T
|
|
13
|
+
}
|
|
14
|
+
interface Rejected {
|
|
15
|
+
status: 'rejected'
|
|
16
|
+
reason: unknown
|
|
17
|
+
}
|
|
18
|
+
interface Pending<T> {
|
|
19
|
+
status: 'pending'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the promise with a value.
|
|
23
|
+
* Will remove the `resolve` and `reject` properties from the promise.
|
|
24
|
+
*/
|
|
25
|
+
resolve: (value: T) => void
|
|
26
|
+
/**
|
|
27
|
+
* Reject the promise with a reason.
|
|
28
|
+
* Will remove the `resolve` and `reject` properties from the promise.
|
|
29
|
+
*/
|
|
30
|
+
reject: (reason: unknown) => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type FulfilledThenable<T> = Promise<T> & Fulfilled<T>
|
|
34
|
+
export type RejectedThenable<T> = Promise<T> & Rejected
|
|
35
|
+
export type PendingThenable<T> = Promise<T> & Pending<T>
|
|
36
|
+
|
|
37
|
+
export type Thenable<T> =
|
|
38
|
+
| FulfilledThenable<T>
|
|
39
|
+
| RejectedThenable<T>
|
|
40
|
+
| PendingThenable<T>
|
|
41
|
+
|
|
42
|
+
export function pendingThenable<T>(): PendingThenable<T> {
|
|
43
|
+
let resolve: Pending<T>['resolve']
|
|
44
|
+
let reject: Pending<T>['reject']
|
|
45
|
+
// this could use `Promise.withResolvers()` in the future
|
|
46
|
+
const thenable = new Promise((_resolve, _reject) => {
|
|
47
|
+
resolve = _resolve
|
|
48
|
+
reject = _reject
|
|
49
|
+
}) as PendingThenable<T>
|
|
50
|
+
|
|
51
|
+
thenable.status = 'pending'
|
|
52
|
+
thenable.catch(() => {
|
|
53
|
+
// prevent unhandled rejection errors
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
function finalize(data: Fulfilled<T> | Rejected) {
|
|
57
|
+
Object.assign(thenable, data)
|
|
58
|
+
|
|
59
|
+
// clear pending props props to avoid calling them twice
|
|
60
|
+
delete (thenable as Partial<PendingThenable<T>>).resolve
|
|
61
|
+
delete (thenable as Partial<PendingThenable<T>>).reject
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
thenable.resolve = (value) => {
|
|
65
|
+
finalize({
|
|
66
|
+
status: 'fulfilled',
|
|
67
|
+
value,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
resolve(value)
|
|
71
|
+
}
|
|
72
|
+
thenable.reject = (reason) => {
|
|
73
|
+
finalize({
|
|
74
|
+
status: 'rejected',
|
|
75
|
+
reason,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
reject(reason)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return thenable
|
|
82
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -380,6 +380,11 @@ export interface QueryObserverOptions<
|
|
|
380
380
|
>
|
|
381
381
|
|
|
382
382
|
_optimisticResults?: 'optimistic' | 'isRestoring'
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Enable prefetching during rendering
|
|
386
|
+
*/
|
|
387
|
+
experimental_prefetchInRender?: boolean
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
export type WithRequired<TTarget, TKey extends keyof TTarget> = TTarget & {
|
|
@@ -687,6 +692,55 @@ export interface QueryObserverBaseResult<
|
|
|
687
692
|
* - See [Network Mode](https://tanstack.com/query/latest/docs/framework/react/guides/network-mode) for more information.
|
|
688
693
|
*/
|
|
689
694
|
fetchStatus: FetchStatus
|
|
695
|
+
/**
|
|
696
|
+
* A stable promise that will be resolved with the data of the query.
|
|
697
|
+
* Requires the `experimental_prefetchInRender` feature flag to be enabled.
|
|
698
|
+
* @example
|
|
699
|
+
*
|
|
700
|
+
* ### Enabling the feature flag
|
|
701
|
+
* ```ts
|
|
702
|
+
* const client = new QueryClient({
|
|
703
|
+
* defaultOptions: {
|
|
704
|
+
* queries: {
|
|
705
|
+
* experimental_prefetchInRender: true,
|
|
706
|
+
* },
|
|
707
|
+
* },
|
|
708
|
+
* })
|
|
709
|
+
* ```
|
|
710
|
+
*
|
|
711
|
+
* ### Usage
|
|
712
|
+
* ```tsx
|
|
713
|
+
* import { useQuery } from '@tanstack/react-query'
|
|
714
|
+
* import React from 'react'
|
|
715
|
+
* import { fetchTodos, type Todo } from './api'
|
|
716
|
+
*
|
|
717
|
+
* function TodoList({ query }: { query: UseQueryResult<Todo[], Error> }) {
|
|
718
|
+
* const data = React.use(query.promise)
|
|
719
|
+
*
|
|
720
|
+
* return (
|
|
721
|
+
* <ul>
|
|
722
|
+
* {data.map(todo => (
|
|
723
|
+
* <li key={todo.id}>{todo.title}</li>
|
|
724
|
+
* ))}
|
|
725
|
+
* </ul>
|
|
726
|
+
* )
|
|
727
|
+
* }
|
|
728
|
+
*
|
|
729
|
+
* export function App() {
|
|
730
|
+
* const query = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
|
|
731
|
+
*
|
|
732
|
+
* return (
|
|
733
|
+
* <>
|
|
734
|
+
* <h1>Todos</h1>
|
|
735
|
+
* <React.Suspense fallback={<div>Loading...</div>}>
|
|
736
|
+
* <TodoList query={query} />
|
|
737
|
+
* </React.Suspense>
|
|
738
|
+
* </>
|
|
739
|
+
* )
|
|
740
|
+
* }
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
promise: Promise<TData>
|
|
690
744
|
}
|
|
691
745
|
|
|
692
746
|
export interface QueryObserverPendingResult<
|