@tanstack/solid-query 5.94.5 → 6.0.0-alpha.1
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/_tsup-dts-rollup.d.cts +17 -8
- package/build/_tsup-dts-rollup.d.ts +17 -8
- package/build/dev.cjs +161 -258
- package/build/dev.d.cts +1 -1
- package/build/dev.d.ts +1 -1
- package/build/dev.js +155 -252
- package/build/index.cjs +161 -258
- package/build/index.d.cts +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +155 -252
- package/package.json +9 -5
- package/src/QueryClientProvider.tsx +7 -16
- package/src/index.ts +1 -1
- package/src/isRestoring.ts +1 -2
- package/src/useBaseQuery.ts +154 -160
- package/src/useMutation.ts +32 -22
- package/src/useMutationState.ts +7 -9
- package/src/useQueries.ts +50 -116
package/src/useBaseQuery.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
// Had to disable the lint rule because isServer type is defined as false
|
|
2
2
|
// in solid-js/web package. I'll create a GitHub issue with them to see
|
|
3
3
|
// why that happens.
|
|
4
|
-
import {
|
|
5
|
-
import { isServer } from 'solid-js/web'
|
|
4
|
+
import { notifyManager, shouldThrowError } from '@tanstack/query-core'
|
|
6
5
|
import {
|
|
7
|
-
createComputed,
|
|
8
6
|
createMemo,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
createRenderEffect,
|
|
8
|
+
createStore,
|
|
9
|
+
isPending,
|
|
12
10
|
onCleanup,
|
|
11
|
+
reconcile,
|
|
12
|
+
refresh,
|
|
13
|
+
snapshot,
|
|
13
14
|
} from 'solid-js'
|
|
14
|
-
import { createStore, reconcile, unwrap } from 'solid-js/store'
|
|
15
15
|
import { useQueryClient } from './QueryClientProvider'
|
|
16
16
|
import { useIsRestoring } from './isRestoring'
|
|
17
17
|
import type { UseBaseQueryOptions } from './types'
|
|
18
|
-
import type { Accessor
|
|
18
|
+
import type { Accessor } from 'solid-js'
|
|
19
19
|
import type { QueryClient } from './QueryClient'
|
|
20
20
|
import type {
|
|
21
21
|
Query,
|
|
@@ -24,6 +24,29 @@ import type {
|
|
|
24
24
|
QueryObserverResult,
|
|
25
25
|
} from '@tanstack/query-core'
|
|
26
26
|
|
|
27
|
+
const isServer = typeof window === 'undefined'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* During SSR, Solid's store is serialized by seroval which cannot handle
|
|
31
|
+
* functions. Strip `refetch`, `fetchNextPage`, and `fetchPreviousPage`
|
|
32
|
+
* from the observer result before it enters the store so serialization
|
|
33
|
+
* succeeds. On the client this is a no-op (returns the object as-is).
|
|
34
|
+
*/
|
|
35
|
+
function _stripFnsForSSR<TData, TError>(
|
|
36
|
+
obj: QueryObserverResult<TData, TError>,
|
|
37
|
+
): QueryObserverResult<TData, TError> {
|
|
38
|
+
if (!isServer) return obj
|
|
39
|
+
const out: Record<string, unknown> = {}
|
|
40
|
+
for (const k of Object.keys(obj)) {
|
|
41
|
+
if (k === 'refetch' || k === 'fetchNextPage' || k === 'fetchPreviousPage') {
|
|
42
|
+
out[k] = undefined
|
|
43
|
+
} else {
|
|
44
|
+
out[k] = (obj as any)[k]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out as unknown as QueryObserverResult<TData, TError>
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
function reconcileFn<TData, TError>(
|
|
28
51
|
store: QueryObserverResult<TData, TError>,
|
|
29
52
|
result: QueryObserverResult<TData, TError>,
|
|
@@ -33,11 +56,15 @@ function reconcileFn<TData, TError>(
|
|
|
33
56
|
| ((oldData: TData | undefined, newData: TData) => TData),
|
|
34
57
|
queryHash?: string,
|
|
35
58
|
): QueryObserverResult<TData, TError> {
|
|
36
|
-
if (reconcileOption === false) return result
|
|
37
59
|
if (typeof reconcileOption === 'function') {
|
|
38
60
|
const newData = reconcileOption(store.data, result.data as TData)
|
|
39
61
|
return { ...result, data: newData } as typeof result
|
|
40
62
|
}
|
|
63
|
+
|
|
64
|
+
if (reconcileOption === false) return result
|
|
65
|
+
|
|
66
|
+
const key = reconcileOption
|
|
67
|
+
|
|
41
68
|
let data = result.data
|
|
42
69
|
if (store.data === undefined) {
|
|
43
70
|
try {
|
|
@@ -55,8 +82,16 @@ function reconcileFn<TData, TError>(
|
|
|
55
82
|
}
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
// reconcile() in Solid 2.0 mutates in place and returns void.
|
|
86
|
+
// We apply it to store.data so the store's nested signals update.
|
|
87
|
+
// On first load (store.data is undefined), there's nothing to reconcile against,
|
|
88
|
+
// so we just return the data as-is.
|
|
89
|
+
if (store.data !== undefined && data !== undefined) {
|
|
90
|
+
reconcile(data, key)(store.data)
|
|
91
|
+
// Return result with the existing store.data reference (now reconciled in place)
|
|
92
|
+
return { ...result, data: store.data } as typeof result
|
|
93
|
+
}
|
|
94
|
+
return { ...result, data } as typeof result
|
|
60
95
|
}
|
|
61
96
|
|
|
62
97
|
/**
|
|
@@ -75,7 +110,7 @@ const hydratableObserverResult = <
|
|
|
75
110
|
) => {
|
|
76
111
|
if (!isServer) return result
|
|
77
112
|
const obj: any = {
|
|
78
|
-
...
|
|
113
|
+
...snapshot(result),
|
|
79
114
|
// During SSR, functions cannot be serialized, so we need to remove them
|
|
80
115
|
// This is safe because we will add these functions back when the query is hydrated
|
|
81
116
|
refetch: undefined,
|
|
@@ -138,15 +173,26 @@ export function useBaseQuery<
|
|
|
138
173
|
}
|
|
139
174
|
return defaultOptions
|
|
140
175
|
})
|
|
141
|
-
const initialOptions = defaultedOptions()
|
|
142
176
|
|
|
143
|
-
const
|
|
144
|
-
|
|
177
|
+
const observer = new Observer(client(), defaultedOptions())
|
|
178
|
+
|
|
179
|
+
// Track options reactively so the queryResource memo re-runs on change.
|
|
180
|
+
const trackedDefaultedOptions = createMemo(() => defaultedOptions())
|
|
181
|
+
|
|
182
|
+
// Apply options in an effect to avoid store writes inside the memo.
|
|
183
|
+
// setOptions triggers updateResult → notify → subscription → setState,
|
|
184
|
+
// which must run in an effect context in Solid v2.
|
|
185
|
+
createRenderEffect(
|
|
186
|
+
() => trackedDefaultedOptions(),
|
|
187
|
+
(opts) => {
|
|
188
|
+
observer.setOptions(opts)
|
|
189
|
+
},
|
|
145
190
|
)
|
|
146
191
|
|
|
147
|
-
let observerResult = observer
|
|
148
|
-
const [state, setState] =
|
|
149
|
-
|
|
192
|
+
let observerResult = observer.getOptimisticResult(defaultedOptions())
|
|
193
|
+
const [state, setState] = createStore<QueryObserverResult<TData, TError>>(
|
|
194
|
+
_stripFnsForSSR(observerResult),
|
|
195
|
+
)
|
|
150
196
|
|
|
151
197
|
const createServerSubscriber = (
|
|
152
198
|
resolve: (
|
|
@@ -154,9 +200,9 @@ export function useBaseQuery<
|
|
|
154
200
|
) => void,
|
|
155
201
|
reject: (reason?: any) => void,
|
|
156
202
|
) => {
|
|
157
|
-
return observer
|
|
203
|
+
return observer.subscribe((result) => {
|
|
158
204
|
notifyManager.batchCalls(() => {
|
|
159
|
-
const query = observer
|
|
205
|
+
const query = observer.getCurrentQuery()
|
|
160
206
|
const unwrappedResult = hydratableObserverResult(query, result)
|
|
161
207
|
|
|
162
208
|
if (result.data !== undefined && unwrappedResult.isError) {
|
|
@@ -178,55 +224,49 @@ export function useBaseQuery<
|
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
const createClientSubscriber = () => {
|
|
181
|
-
|
|
182
|
-
|
|
227
|
+
return observer.subscribe((result) => {
|
|
228
|
+
const previousResult = observerResult
|
|
183
229
|
observerResult = result
|
|
230
|
+
setStateWithReconciliation(result)
|
|
184
231
|
queueMicrotask(() => {
|
|
185
|
-
if (
|
|
186
|
-
|
|
232
|
+
if (
|
|
233
|
+
unsubscribe &&
|
|
234
|
+
!disposed &&
|
|
235
|
+
(previousResult.isLoading !== result.isLoading ||
|
|
236
|
+
previousResult.isError !== result.isError)
|
|
237
|
+
) {
|
|
238
|
+
try {
|
|
239
|
+
refresh(queryResource)
|
|
240
|
+
} catch {
|
|
241
|
+
// NotReadyError is expected when refreshing a memo that returns
|
|
242
|
+
// a Promise. The Loading boundary handles this during rendering,
|
|
243
|
+
// but when refresh is called from a microtask there is no boundary.
|
|
244
|
+
}
|
|
187
245
|
}
|
|
188
246
|
})
|
|
189
247
|
})
|
|
190
248
|
}
|
|
191
249
|
|
|
192
250
|
function setStateWithReconciliation(res: typeof observerResult) {
|
|
193
|
-
const opts = observer
|
|
194
|
-
|
|
195
|
-
const
|
|
251
|
+
const opts = observer.options
|
|
252
|
+
const reconcileOptions = (opts as any).reconcile
|
|
253
|
+
const sanitized = _stripFnsForSSR(res)
|
|
196
254
|
|
|
197
255
|
setState((store) => {
|
|
198
256
|
return reconcileFn(
|
|
199
257
|
store,
|
|
200
|
-
|
|
258
|
+
sanitized,
|
|
201
259
|
reconcileOptions === undefined ? false : reconcileOptions,
|
|
202
260
|
opts.queryHash,
|
|
203
261
|
)
|
|
204
262
|
})
|
|
205
263
|
}
|
|
206
264
|
|
|
207
|
-
function createDeepSignal<T>(): Signal<T> {
|
|
208
|
-
return [
|
|
209
|
-
() => state,
|
|
210
|
-
(v: any) => {
|
|
211
|
-
const unwrapped = unwrap(state)
|
|
212
|
-
if (typeof v === 'function') {
|
|
213
|
-
v = v(unwrapped)
|
|
214
|
-
}
|
|
215
|
-
// Hydration data exists on first load after SSR,
|
|
216
|
-
// and should be removed from the observer result
|
|
217
|
-
if (v?.hydrationData) {
|
|
218
|
-
const { hydrationData, ...rest } = v
|
|
219
|
-
v = rest
|
|
220
|
-
}
|
|
221
|
-
setStateWithReconciliation(v)
|
|
222
|
-
},
|
|
223
|
-
] as Signal<T>
|
|
224
|
-
}
|
|
225
|
-
|
|
226
265
|
/**
|
|
227
266
|
* Unsubscribe is set lazily, so that we can subscribe after hydration when needed.
|
|
228
267
|
*/
|
|
229
268
|
let unsubscribe: (() => void) | null = null
|
|
269
|
+
let disposed = false
|
|
230
270
|
|
|
231
271
|
/*
|
|
232
272
|
Fixes #7275
|
|
@@ -236,115 +276,58 @@ export function useBaseQuery<
|
|
|
236
276
|
but the resource is still in a loading state
|
|
237
277
|
*/
|
|
238
278
|
let resolver: ((value: ResourceData) => void) | null = null
|
|
239
|
-
const
|
|
279
|
+
const queryResource = createMemo<ResourceData>(
|
|
240
280
|
() => {
|
|
241
|
-
|
|
281
|
+
// Read trackedDefaultedOptions to ensure this memo re-runs when options change
|
|
282
|
+
const opts = trackedDefaultedOptions()
|
|
283
|
+
// Read isRestoring unconditionally so the memo re-runs when it changes
|
|
284
|
+
const restoring = isRestoring()
|
|
285
|
+
|
|
242
286
|
return new Promise((resolve, reject) => {
|
|
243
287
|
resolver = resolve
|
|
244
288
|
if (isServer) {
|
|
245
|
-
unsubscribe = createServerSubscriber(
|
|
246
|
-
|
|
289
|
+
unsubscribe = createServerSubscriber((data) => {
|
|
290
|
+
resolve(data as ResourceData)
|
|
291
|
+
}, reject)
|
|
292
|
+
} else if (!unsubscribe && !restoring) {
|
|
247
293
|
unsubscribe = createClientSubscriber()
|
|
248
294
|
}
|
|
249
|
-
|
|
295
|
+
// Use getOptimisticResult instead of updateResult to keep the memo
|
|
296
|
+
// free of store writes (updateResult triggers notify → setState).
|
|
297
|
+
const currentResult = observer.getOptimisticResult(opts)
|
|
298
|
+
observerResult = currentResult
|
|
250
299
|
|
|
251
300
|
if (
|
|
252
|
-
|
|
253
|
-
!
|
|
254
|
-
!
|
|
255
|
-
shouldThrowError(
|
|
256
|
-
|
|
257
|
-
|
|
301
|
+
currentResult.isError &&
|
|
302
|
+
!currentResult.isFetching &&
|
|
303
|
+
!restoring &&
|
|
304
|
+
shouldThrowError(opts.throwOnError, [
|
|
305
|
+
currentResult.error,
|
|
306
|
+
observer.getCurrentQuery(),
|
|
258
307
|
])
|
|
259
308
|
) {
|
|
260
|
-
setStateWithReconciliation(
|
|
261
|
-
return reject(
|
|
309
|
+
setStateWithReconciliation(currentResult)
|
|
310
|
+
return reject(currentResult.error)
|
|
262
311
|
}
|
|
263
|
-
if (!
|
|
312
|
+
if (!currentResult.isLoading) {
|
|
264
313
|
resolver = null
|
|
314
|
+
setStateWithReconciliation(currentResult)
|
|
265
315
|
return resolve(
|
|
266
|
-
hydratableObserverResult(
|
|
316
|
+
hydratableObserverResult(observer.getCurrentQuery(), currentResult),
|
|
267
317
|
)
|
|
268
318
|
}
|
|
269
319
|
|
|
270
|
-
|
|
320
|
+
// Defer the loading-state store write so it runs outside the memo.
|
|
321
|
+
queueMicrotask(() => setStateWithReconciliation(currentResult))
|
|
271
322
|
})
|
|
272
323
|
},
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
get deferStream() {
|
|
277
|
-
return options().deferStream
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* If this resource was populated on the server (either sync render, or streamed in over time), onHydrated
|
|
282
|
-
* will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber.
|
|
283
|
-
*
|
|
284
|
-
* Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support.
|
|
285
|
-
*
|
|
286
|
-
* Note that this is only invoked on the client, for queries that were originally run on the server.
|
|
287
|
-
*/
|
|
288
|
-
onHydrated(_k, info) {
|
|
289
|
-
if (info.value && 'hydrationData' in info.value) {
|
|
290
|
-
hydrate(client(), {
|
|
291
|
-
// @ts-expect-error - hydrationData is not correctly typed internally
|
|
292
|
-
queries: [{ ...info.value.hydrationData }],
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (unsubscribe) return
|
|
297
|
-
/**
|
|
298
|
-
* Do not refetch query on mount if query was fetched on server,
|
|
299
|
-
* even if `staleTime` is not set.
|
|
300
|
-
*/
|
|
301
|
-
const newOptions = { ...initialOptions }
|
|
302
|
-
if (
|
|
303
|
-
(initialOptions.staleTime || !initialOptions.initialData) &&
|
|
304
|
-
info.value
|
|
305
|
-
) {
|
|
306
|
-
newOptions.refetchOnMount = false
|
|
307
|
-
}
|
|
308
|
-
// Setting the options as an immutable object to prevent
|
|
309
|
-
// wonky behavior with observer subscriptions
|
|
310
|
-
observer().setOptions(newOptions)
|
|
311
|
-
setStateWithReconciliation(observer().getOptimisticResult(newOptions))
|
|
312
|
-
unsubscribe = createClientSubscriber()
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
createComputed(
|
|
318
|
-
on(
|
|
319
|
-
client,
|
|
320
|
-
(c) => {
|
|
321
|
-
if (unsubscribe) {
|
|
322
|
-
unsubscribe()
|
|
323
|
-
}
|
|
324
|
-
const newObserver = new Observer(c, defaultedOptions())
|
|
325
|
-
unsubscribe = createClientSubscriber()
|
|
326
|
-
setObserver(newObserver)
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
defer: true,
|
|
330
|
-
},
|
|
331
|
-
),
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
createComputed(
|
|
335
|
-
on(
|
|
336
|
-
isRestoring,
|
|
337
|
-
(restoring) => {
|
|
338
|
-
if (!restoring && !isServer) {
|
|
339
|
-
refetch()
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
{ defer: true },
|
|
343
|
-
),
|
|
324
|
+
observerResult,
|
|
325
|
+
{ ssrSource: 'client' },
|
|
344
326
|
)
|
|
345
327
|
|
|
346
328
|
onCleanup(() => {
|
|
347
|
-
|
|
329
|
+
disposed = true
|
|
330
|
+
if (isServer && isPending(queryResource)) {
|
|
348
331
|
unsubscribeQueued = true
|
|
349
332
|
return
|
|
350
333
|
}
|
|
@@ -358,32 +341,43 @@ export function useBaseQuery<
|
|
|
358
341
|
}
|
|
359
342
|
})
|
|
360
343
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
)
|
|
344
|
+
// Properties that should never throw — these let users access error info
|
|
345
|
+
// even outside an ErrorBoundary.
|
|
346
|
+
const errorPassthroughProps = new Set([
|
|
347
|
+
'error',
|
|
348
|
+
'isError',
|
|
349
|
+
'failureCount',
|
|
350
|
+
'failureReason',
|
|
351
|
+
'errorUpdateCount',
|
|
352
|
+
'errorUpdatedAt',
|
|
353
|
+
])
|
|
372
354
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (state.data !== undefined) {
|
|
380
|
-
return queryResource.latest?.data
|
|
381
|
-
}
|
|
382
|
-
return queryResource()?.data
|
|
355
|
+
// Return a proxy that throws on property access when throwOnError is enabled
|
|
356
|
+
return new Proxy(state, {
|
|
357
|
+
get(target, prop, receiver) {
|
|
358
|
+
// Always pass through symbols (needed for store internals, iteration, etc.)
|
|
359
|
+
if (typeof prop === 'symbol') {
|
|
360
|
+
return Reflect.get(target, prop, receiver)
|
|
383
361
|
}
|
|
384
|
-
return Reflect.get(target, prop)
|
|
385
|
-
},
|
|
386
|
-
}
|
|
387
362
|
|
|
388
|
-
|
|
363
|
+
// Always pass through error-related props without throwing
|
|
364
|
+
if (errorPassthroughProps.has(prop)) {
|
|
365
|
+
return Reflect.get(target, prop, receiver)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Check throwOnError condition before returning the value
|
|
369
|
+
if (
|
|
370
|
+
state.isError &&
|
|
371
|
+
!state.isFetching &&
|
|
372
|
+
shouldThrowError(observer.options.throwOnError, [
|
|
373
|
+
state.error,
|
|
374
|
+
observer.getCurrentQuery(),
|
|
375
|
+
])
|
|
376
|
+
) {
|
|
377
|
+
throw state.error
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return Reflect.get(target, prop, receiver)
|
|
381
|
+
},
|
|
382
|
+
})
|
|
389
383
|
}
|
package/src/useMutation.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { MutationObserver, noop, shouldThrowError } from '@tanstack/query-core'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
createMemo,
|
|
4
|
+
createRenderEffect,
|
|
5
|
+
createStore,
|
|
6
|
+
onCleanup,
|
|
7
|
+
} from 'solid-js'
|
|
4
8
|
import { useQueryClient } from './QueryClientProvider'
|
|
5
9
|
import type { DefaultError } from '@tanstack/query-core'
|
|
6
10
|
import type { QueryClient } from './QueryClient'
|
|
@@ -30,6 +34,11 @@ export function useMutation<
|
|
|
30
34
|
TOnMutateResult
|
|
31
35
|
>(client(), options())
|
|
32
36
|
|
|
37
|
+
// Track options changes and update observer
|
|
38
|
+
createMemo(() => {
|
|
39
|
+
observer.setOptions(options())
|
|
40
|
+
})
|
|
41
|
+
|
|
33
42
|
const mutate: UseMutateFunction<
|
|
34
43
|
TData,
|
|
35
44
|
TError,
|
|
@@ -47,33 +56,34 @@ export function useMutation<
|
|
|
47
56
|
mutateAsync: observer.getCurrentResult().mutate,
|
|
48
57
|
})
|
|
49
58
|
|
|
50
|
-
createComputed(() => {
|
|
51
|
-
observer.setOptions(options())
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
createComputed(
|
|
55
|
-
on(
|
|
56
|
-
() => state.status,
|
|
57
|
-
() => {
|
|
58
|
-
if (
|
|
59
|
-
state.isError &&
|
|
60
|
-
shouldThrowError(observer.options.throwOnError, [state.error])
|
|
61
|
-
) {
|
|
62
|
-
throw state.error
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
59
|
const unsubscribe = observer.subscribe((result) => {
|
|
69
|
-
setState({
|
|
60
|
+
setState(() => ({
|
|
70
61
|
...result,
|
|
71
62
|
mutate,
|
|
72
63
|
mutateAsync: result.mutate,
|
|
73
|
-
})
|
|
64
|
+
}))
|
|
74
65
|
})
|
|
75
66
|
|
|
76
67
|
onCleanup(unsubscribe)
|
|
77
68
|
|
|
69
|
+
// Use createRenderEffect to throw errors when throwOnError is set.
|
|
70
|
+
// The throw must happen in the compute function (first arg), not the effect
|
|
71
|
+
// function, so that the error goes through notifyStatus and gets wrapped as
|
|
72
|
+
// a StatusError with a source — which is required for <Errored> boundaries
|
|
73
|
+
// to capture it via CollectionQueue.notify.
|
|
74
|
+
createRenderEffect(
|
|
75
|
+
() => {
|
|
76
|
+
const isError = state.isError
|
|
77
|
+
const error = state.error
|
|
78
|
+
if (
|
|
79
|
+
isError &&
|
|
80
|
+
shouldThrowError(observer.options.throwOnError, [error as TError])
|
|
81
|
+
) {
|
|
82
|
+
throw error
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
() => {},
|
|
86
|
+
)
|
|
87
|
+
|
|
78
88
|
return state
|
|
79
89
|
}
|
package/src/useMutationState.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createMemo, createSignal, onCleanup } from 'solid-js'
|
|
2
2
|
import { replaceEqualDeep } from '@tanstack/query-core'
|
|
3
3
|
import { useQueryClient } from './QueryClientProvider'
|
|
4
4
|
import type {
|
|
@@ -38,19 +38,17 @@ export function useMutationState<TResult = MutationState>(
|
|
|
38
38
|
getResult(mutationCache(), options()),
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const unsubscribe = mutationCache().subscribe(() => {
|
|
42
|
+
setResult((prev) => {
|
|
43
43
|
const nextResult = replaceEqualDeep(
|
|
44
|
-
|
|
44
|
+
prev,
|
|
45
45
|
getResult(mutationCache(), options()),
|
|
46
46
|
)
|
|
47
|
-
|
|
48
|
-
setResult(nextResult)
|
|
49
|
-
}
|
|
47
|
+
return prev === nextResult ? prev : nextResult
|
|
50
48
|
})
|
|
51
|
-
|
|
52
|
-
onCleanup(unsubscribe)
|
|
53
49
|
})
|
|
54
50
|
|
|
51
|
+
onCleanup(unsubscribe)
|
|
52
|
+
|
|
55
53
|
return result
|
|
56
54
|
}
|