@effector-tanstack-query/react 0.3.1 → 0.5.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/dist/index.cjs +231 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -3
- package/dist/index.d.ts +42 -3
- package/dist/index.js +228 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +408 -0
package/src/index.ts
CHANGED
|
@@ -8,13 +8,20 @@ import type {
|
|
|
8
8
|
FetchStatus,
|
|
9
9
|
HydrateOptions,
|
|
10
10
|
MutateOptions,
|
|
11
|
+
MutationFilters,
|
|
12
|
+
QueryFilters,
|
|
11
13
|
QueryStatus,
|
|
12
14
|
} from '@tanstack/query-core'
|
|
15
|
+
|
|
16
|
+
// Re-exported for convenience so consumers can type filter arguments without a
|
|
17
|
+
// direct `@tanstack/query-core` import.
|
|
18
|
+
export type { QueryFilters, MutationFilters } from '@tanstack/query-core'
|
|
13
19
|
import { $queryClient } from '@effector-tanstack-query/core'
|
|
14
20
|
import type {
|
|
15
21
|
InfiniteQueryResult,
|
|
16
22
|
MutationResult,
|
|
17
23
|
MutationStatus,
|
|
24
|
+
QueriesResult,
|
|
18
25
|
QueryResult,
|
|
19
26
|
} from '@effector-tanstack-query/core'
|
|
20
27
|
|
|
@@ -166,6 +173,62 @@ export function useMutation<TData = unknown, TError = Error, TVariables = void>(
|
|
|
166
173
|
return { ...state, mutate, mutateWith, reset }
|
|
167
174
|
}
|
|
168
175
|
|
|
176
|
+
// =============================================================================
|
|
177
|
+
// useIsFetching / useIsMutating — global in-flight counters for the scope's
|
|
178
|
+
// QueryClient. Thin React-subscription wrappers over the QueryClient's own
|
|
179
|
+
// `isFetching()` / `isMutating()` — the counting is done by tanstack, not here.
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Number of queries currently fetching in the scope's `QueryClient`. Use for
|
|
184
|
+
* global indicators — top-bar spinners, tray badges, etc.
|
|
185
|
+
*
|
|
186
|
+
* Pass `QueryFilters` to scope the count (e.g. `{ queryKey: ['user'] }`).
|
|
187
|
+
* Filters are applied by value on every notification, so passing a fresh
|
|
188
|
+
* object literal each render is fine — no `useMemo` needed.
|
|
189
|
+
*
|
|
190
|
+
* Resolves the client via `useUnit($queryClient)`, so each fork scope counts
|
|
191
|
+
* its own in-flight queries. Returns `0` when no client is set (e.g. an
|
|
192
|
+
* RSC/SSR pass with no scope client) instead of throwing.
|
|
193
|
+
*/
|
|
194
|
+
export function useIsFetching(filters?: QueryFilters): number {
|
|
195
|
+
const qc = useUnit($queryClient)
|
|
196
|
+
// Keep filters out of the subscribe dependency so a fresh literal each render
|
|
197
|
+
// doesn't re-subscribe; getSnapshot reads the latest via the ref.
|
|
198
|
+
const filtersRef = React.useRef(filters)
|
|
199
|
+
filtersRef.current = filters
|
|
200
|
+
|
|
201
|
+
return React.useSyncExternalStore(
|
|
202
|
+
React.useCallback(
|
|
203
|
+
(notify) => qc?.getQueryCache().subscribe(notify) ?? (() => {}),
|
|
204
|
+
[qc],
|
|
205
|
+
),
|
|
206
|
+
() => qc?.isFetching(filtersRef.current) ?? 0,
|
|
207
|
+
() => 0,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Number of mutations currently running in the scope's `QueryClient`. Mirrors
|
|
213
|
+
* {@link useIsFetching} for mutations.
|
|
214
|
+
*
|
|
215
|
+
* Pass `MutationFilters` to scope the count (e.g. `{ mutationKey: ['createUser'] }`).
|
|
216
|
+
*/
|
|
217
|
+
export function useIsMutating(filters?: MutationFilters): number {
|
|
218
|
+
const qc = useUnit($queryClient)
|
|
219
|
+
const filtersRef = React.useRef(filters)
|
|
220
|
+
filtersRef.current = filters
|
|
221
|
+
|
|
222
|
+
return React.useSyncExternalStore(
|
|
223
|
+
React.useCallback(
|
|
224
|
+
(notify) => qc?.getMutationCache().subscribe(notify) ?? (() => {}),
|
|
225
|
+
[qc],
|
|
226
|
+
),
|
|
227
|
+
() => qc?.isMutating(filtersRef.current) ?? 0,
|
|
228
|
+
() => 0,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
169
232
|
export interface UseInfiniteQueryResult<TData, TError> {
|
|
170
233
|
data: TData | undefined
|
|
171
234
|
error: TError | null
|
|
@@ -226,6 +289,119 @@ export function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(
|
|
|
226
289
|
return { ...state, refresh, fetchNextPage, fetchPreviousPage }
|
|
227
290
|
}
|
|
228
291
|
|
|
292
|
+
// =============================================================================
|
|
293
|
+
// useQueries — parallel reads across a static tuple of factories OR a family
|
|
294
|
+
// produced by `createQueries({ source, query })`.
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
type UseQueriesTuple = ReadonlyArray<QueryResult<any, any>>
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Maps a tuple of `QueryResult<TData, TError>` to a tuple of
|
|
301
|
+
* `UseQueryResult<TData, TError>`, preserving per-element types so
|
|
302
|
+
* destructuring (`const [user, posts] = useQueries([...] as const)`)
|
|
303
|
+
* yields fully-typed entries.
|
|
304
|
+
*/
|
|
305
|
+
export type UseQueriesTupleResult<T extends UseQueriesTuple> = {
|
|
306
|
+
[K in keyof T]: T[K] extends QueryResult<infer D, infer E>
|
|
307
|
+
? UseQueryResult<D, E>
|
|
308
|
+
: never
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function useQueries<const T extends UseQueriesTuple>(
|
|
312
|
+
queries: T,
|
|
313
|
+
): UseQueriesTupleResult<T>
|
|
314
|
+
export function useQueries<TItem, TData, TError>(
|
|
315
|
+
family: QueriesResult<TItem, TData, TError>,
|
|
316
|
+
): ReadonlyArray<UseQueryResult<TData, TError>>
|
|
317
|
+
export function useQueries(
|
|
318
|
+
arg: UseQueriesTuple | QueriesResult<unknown, unknown, unknown>,
|
|
319
|
+
): UseQueriesTupleResult<UseQueriesTuple> | ReadonlyArray<UseQueryResult<unknown, unknown>> {
|
|
320
|
+
if (Array.isArray(arg)) {
|
|
321
|
+
return useQueriesTuple(arg)
|
|
322
|
+
}
|
|
323
|
+
return useQueriesFamily(arg as QueriesResult<unknown, unknown, unknown>)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function useQueriesTuple<T extends UseQueriesTuple>(
|
|
327
|
+
queries: T,
|
|
328
|
+
): UseQueriesTupleResult<T> {
|
|
329
|
+
// Twelve `useUnit` calls — count is FIXED, independent of N. The
|
|
330
|
+
// array argument may grow / shrink between renders; effector-react
|
|
331
|
+
// re-subscribes the underlying stores transparently.
|
|
332
|
+
//
|
|
333
|
+
// Rules-of-hooks constraint: the SHAPE of the call list must be
|
|
334
|
+
// stable, not the length of each input array. We satisfy this with a
|
|
335
|
+
// fixed sequence of 9 state-store calls + 3 event-bind calls.
|
|
336
|
+
const datas = useUnit(queries.map((q) => q.$data))
|
|
337
|
+
const errors = useUnit(queries.map((q) => q.$error))
|
|
338
|
+
const statuses = useUnit(queries.map((q) => q.$status))
|
|
339
|
+
const isPendings = useUnit(queries.map((q) => q.$isPending))
|
|
340
|
+
const isFetchings = useUnit(queries.map((q) => q.$isFetching))
|
|
341
|
+
const isSuccesses = useUnit(queries.map((q) => q.$isSuccess))
|
|
342
|
+
const isErrors = useUnit(queries.map((q) => q.$isError))
|
|
343
|
+
const isPlaceholderDatas = useUnit(queries.map((q) => q.$isPlaceholderData))
|
|
344
|
+
const fetchStatuses = useUnit(queries.map((q) => q.$fetchStatus))
|
|
345
|
+
|
|
346
|
+
const mounts = useUnit(queries.map((q) => q.mounted))
|
|
347
|
+
const unmounts = useUnit(queries.map((q) => q.unmounted))
|
|
348
|
+
const refreshes = useUnit(queries.map((q) => q.refresh))
|
|
349
|
+
|
|
350
|
+
React.useEffect(() => {
|
|
351
|
+
for (const m of mounts) m()
|
|
352
|
+
return () => {
|
|
353
|
+
for (const u of unmounts) u()
|
|
354
|
+
}
|
|
355
|
+
// The dep on length re-runs mount/unmount when the consumer swaps
|
|
356
|
+
// out the factory set (rare; most consumers pass a stable `as const`
|
|
357
|
+
// literal). Function refs from `useUnit` are stable per (unit, scope)
|
|
358
|
+
// — depending on their array identity would re-fire every render.
|
|
359
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
360
|
+
}, [queries.length])
|
|
361
|
+
|
|
362
|
+
return queries.map((_, i) => ({
|
|
363
|
+
data: datas[i],
|
|
364
|
+
error: errors[i],
|
|
365
|
+
status: statuses[i],
|
|
366
|
+
isPending: isPendings[i],
|
|
367
|
+
isFetching: isFetchings[i],
|
|
368
|
+
isSuccess: isSuccesses[i],
|
|
369
|
+
isError: isErrors[i],
|
|
370
|
+
isPlaceholderData: isPlaceholderDatas[i],
|
|
371
|
+
fetchStatus: fetchStatuses[i],
|
|
372
|
+
refresh: refreshes[i],
|
|
373
|
+
})) as UseQueriesTupleResult<T>
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function useQueriesFamily<TItem, TData, TError>(
|
|
377
|
+
family: QueriesResult<TItem, TData, TError>,
|
|
378
|
+
): ReadonlyArray<UseQueryResult<TData, TError>> {
|
|
379
|
+
const items = useUnit(family.$items)
|
|
380
|
+
const mount = useUnit(family.mounted)
|
|
381
|
+
const unmount = useUnit(family.unmounted)
|
|
382
|
+
const refreshOne = useUnit(family.refreshOne)
|
|
383
|
+
|
|
384
|
+
React.useEffect(() => {
|
|
385
|
+
mount()
|
|
386
|
+
return () => unmount()
|
|
387
|
+
}, [mount, unmount])
|
|
388
|
+
|
|
389
|
+
return items.map((it) => ({
|
|
390
|
+
data: it.data,
|
|
391
|
+
error: it.error,
|
|
392
|
+
status: it.status,
|
|
393
|
+
isPending: it.isPending,
|
|
394
|
+
isFetching: it.isFetching,
|
|
395
|
+
isSuccess: it.isSuccess,
|
|
396
|
+
isError: it.isError,
|
|
397
|
+
isPlaceholderData: it.isPlaceholderData,
|
|
398
|
+
fetchStatus: it.fetchStatus,
|
|
399
|
+
// Per-item refresh — routes through the family's `refreshOne(item)`
|
|
400
|
+
// so the consumer doesn't have to thread the source manually.
|
|
401
|
+
refresh: () => refreshOne(it.source),
|
|
402
|
+
}))
|
|
403
|
+
}
|
|
404
|
+
|
|
229
405
|
// Suspense data path: read from a per-scope observer.
|
|
230
406
|
//
|
|
231
407
|
// The scope mount chain runs in useEffect, which is skipped while a component
|
|
@@ -575,3 +751,235 @@ function useSuspenseObserver<
|
|
|
575
751
|
// observer to throw `fetchOptimistic` on.
|
|
576
752
|
return observerInScope ?? transient
|
|
577
753
|
}
|
|
754
|
+
|
|
755
|
+
// =============================================================================
|
|
756
|
+
// useSuspenseQueries — Suspense variant of `useQueries`. Same two overloads:
|
|
757
|
+
// static tuple of factories OR a family from `createQueries(...)`.
|
|
758
|
+
// =============================================================================
|
|
759
|
+
|
|
760
|
+
type UseSuspenseQueriesTuple = ReadonlyArray<QueryResult<any, any>>
|
|
761
|
+
|
|
762
|
+
export type UseSuspenseQueriesTupleResult<T extends UseSuspenseQueriesTuple> = {
|
|
763
|
+
[K in keyof T]: T[K] extends QueryResult<infer D, infer E>
|
|
764
|
+
? UseSuspenseQueryResult<D, E>
|
|
765
|
+
: never
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export function useSuspenseQueries<const T extends UseSuspenseQueriesTuple>(
|
|
769
|
+
queries: T,
|
|
770
|
+
): UseSuspenseQueriesTupleResult<T>
|
|
771
|
+
export function useSuspenseQueries<TItem, TData, TError>(
|
|
772
|
+
family: QueriesResult<TItem, TData, TError>,
|
|
773
|
+
): ReadonlyArray<UseSuspenseQueryResult<TData, TError>>
|
|
774
|
+
export function useSuspenseQueries(
|
|
775
|
+
arg: UseSuspenseQueriesTuple | QueriesResult<unknown, unknown, unknown>,
|
|
776
|
+
):
|
|
777
|
+
| UseSuspenseQueriesTupleResult<UseSuspenseQueriesTuple>
|
|
778
|
+
| ReadonlyArray<UseSuspenseQueryResult<unknown, unknown>> {
|
|
779
|
+
if (Array.isArray(arg)) {
|
|
780
|
+
return useSuspenseQueriesTuple(arg)
|
|
781
|
+
}
|
|
782
|
+
return useSuspenseQueriesFamily(
|
|
783
|
+
arg as QueriesResult<unknown, unknown, unknown>,
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function useSuspenseQueriesTuple<T extends UseSuspenseQueriesTuple>(
|
|
788
|
+
queries: T,
|
|
789
|
+
): UseSuspenseQueriesTupleResult<T> {
|
|
790
|
+
// Mount lifecycle (same as `useQueriesTuple`).
|
|
791
|
+
const mounts = useUnit(queries.map((q) => q.mounted))
|
|
792
|
+
const unmounts = useUnit(queries.map((q) => q.unmounted))
|
|
793
|
+
const refreshes = useUnit(queries.map((q) => q.refresh))
|
|
794
|
+
React.useEffect(() => {
|
|
795
|
+
for (const m of mounts) m()
|
|
796
|
+
return () => {
|
|
797
|
+
for (const u of unmounts) u()
|
|
798
|
+
}
|
|
799
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
800
|
+
}, [queries.length])
|
|
801
|
+
|
|
802
|
+
// Per-query state from stores — fixed hook count.
|
|
803
|
+
const datas = useUnit(queries.map((q) => q.$data))
|
|
804
|
+
const errors = useUnit(queries.map((q) => q.$error))
|
|
805
|
+
const statuses = useUnit(queries.map((q) => q.$status))
|
|
806
|
+
const isFetchings = useUnit(queries.map((q) => q.$isFetching))
|
|
807
|
+
const isPlaceholderDatas = useUnit(queries.map((q) => q.$isPlaceholderData))
|
|
808
|
+
const fetchStatuses = useUnit(queries.map((q) => q.$fetchStatus))
|
|
809
|
+
|
|
810
|
+
// Observer / qc / key info per query — same fixed-count pattern.
|
|
811
|
+
const observersInScope = useUnit(queries.map((q) => q.$observer))
|
|
812
|
+
const qcs = useUnit(queries.map((q) => q.$queryClient))
|
|
813
|
+
const resolvedKeys = useUnit(
|
|
814
|
+
queries.map((q) => (q as unknown as SuspenseFactory<unknown>).__resolvedKey),
|
|
815
|
+
)
|
|
816
|
+
const enabledStates = useUnit(
|
|
817
|
+
queries.map((q) => (q as unknown as SuspenseFactory<unknown>).__enabled),
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
// Transient observers per slot (null when an in-scope observer
|
|
821
|
+
// exists or no qc is available). One useMemo across all queries —
|
|
822
|
+
// hook-count stable.
|
|
823
|
+
const transients = React.useMemo(() => {
|
|
824
|
+
return queries.map((q, i) => {
|
|
825
|
+
if (observersInScope[i]) return null
|
|
826
|
+
const qc = qcs[i]
|
|
827
|
+
if (!qc) return null
|
|
828
|
+
return (q as unknown as SuspenseFactory<any>).__createObserver(qc, {
|
|
829
|
+
queryKey: resolvedKeys[i],
|
|
830
|
+
enabled: enabledStates[i] as boolean,
|
|
831
|
+
})
|
|
832
|
+
})
|
|
833
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
834
|
+
}, [queries, ...observersInScope, ...qcs, ...resolvedKeys, ...enabledStates])
|
|
835
|
+
|
|
836
|
+
type SuspendableObserver = {
|
|
837
|
+
options: { queryKey: unknown }
|
|
838
|
+
subscribe(cb: () => void): () => void
|
|
839
|
+
fetchOptimistic(options: any): Promise<unknown>
|
|
840
|
+
getOptimisticResult(options: any): {
|
|
841
|
+
status: 'pending' | 'success' | 'error'
|
|
842
|
+
data: unknown
|
|
843
|
+
error: unknown
|
|
844
|
+
isFetching: boolean
|
|
845
|
+
isPlaceholderData: boolean
|
|
846
|
+
fetchStatus: FetchStatus
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const observers = queries.map(
|
|
850
|
+
(_, i) =>
|
|
851
|
+
((observersInScope[i] ?? transients[i]) ?? null) as
|
|
852
|
+
| SuspendableObserver
|
|
853
|
+
| null,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
// Subscribe to every live observer in one effect so the consumer
|
|
857
|
+
// re-renders when ANY of them notifies.
|
|
858
|
+
const [, forceRender] = React.useReducer((x: number) => x + 1, 0)
|
|
859
|
+
React.useEffect(() => {
|
|
860
|
+
const unsubs = observers.map((obs) =>
|
|
861
|
+
obs ? obs.subscribe(forceRender) : null,
|
|
862
|
+
)
|
|
863
|
+
return () => {
|
|
864
|
+
for (const u of unsubs) u?.()
|
|
865
|
+
}
|
|
866
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
867
|
+
}, [queries.length, ...observers])
|
|
868
|
+
|
|
869
|
+
// Per-slot live result — from observer if available (synchronous
|
|
870
|
+
// QueryCache read), otherwise null and we fall back to the
|
|
871
|
+
// effector-store snapshot. Mirrors the dual path in
|
|
872
|
+
// `useSuspenseQuery` so SSR scopes (`$observer` + `$queryClient`
|
|
873
|
+
// both null) keep working.
|
|
874
|
+
const liveResults = observers.map((obs) =>
|
|
875
|
+
obs ? obs.getOptimisticResult(obs.options) : null,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
// Errors first — first error wins.
|
|
879
|
+
for (let i = 0; i < queries.length; i++) {
|
|
880
|
+
const live = liveResults[i]
|
|
881
|
+
if (live) {
|
|
882
|
+
if (live.status === 'error') throw live.error
|
|
883
|
+
} else if (statuses[i] === 'error') {
|
|
884
|
+
throw errors[i]
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Then pending — collect every pending query's inflight promise into
|
|
889
|
+
// a single `Promise.all` thrown to Suspense. The store-only path has
|
|
890
|
+
// no observer to fetch with: that's a misconfiguration (no QC, no
|
|
891
|
+
// prefetch), throw a clear error.
|
|
892
|
+
const pendingPromises: Array<Promise<unknown>> = []
|
|
893
|
+
for (let i = 0; i < queries.length; i++) {
|
|
894
|
+
const live = liveResults[i]
|
|
895
|
+
const obs = observers[i]
|
|
896
|
+
if (live) {
|
|
897
|
+
if (live.status === 'pending' && obs) {
|
|
898
|
+
pendingPromises.push(obs.fetchOptimistic(obs.options))
|
|
899
|
+
}
|
|
900
|
+
} else if (statuses[i] === 'pending') {
|
|
901
|
+
throw new Error(
|
|
902
|
+
'[@effector-tanstack-query/react] useSuspenseQueries: no QueryClient is set. ' +
|
|
903
|
+
'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',
|
|
904
|
+
)
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (pendingPromises.length > 0) throw Promise.all(pendingPromises)
|
|
908
|
+
|
|
909
|
+
// All success — shape the result tuple. Prefer the observer's live
|
|
910
|
+
// result when present (reflects in-flight refetches), fall back to
|
|
911
|
+
// the store snapshot otherwise.
|
|
912
|
+
return queries.map((_, i) => {
|
|
913
|
+
const live = liveResults[i]
|
|
914
|
+
return {
|
|
915
|
+
data: (live ? live.data : datas[i]) as unknown,
|
|
916
|
+
error: (live ? live.error : (errors[i] ?? null)) as unknown,
|
|
917
|
+
status: 'success' as const,
|
|
918
|
+
isPending: false as const,
|
|
919
|
+
isSuccess: true as const,
|
|
920
|
+
isError: false as const,
|
|
921
|
+
isFetching: (live ? live.isFetching : isFetchings[i]) as boolean,
|
|
922
|
+
isPlaceholderData: (live
|
|
923
|
+
? live.isPlaceholderData
|
|
924
|
+
: isPlaceholderDatas[i]) as boolean,
|
|
925
|
+
fetchStatus: (live ? live.fetchStatus : fetchStatuses[i]) as FetchStatus,
|
|
926
|
+
refresh: refreshes[i] as () => void,
|
|
927
|
+
}
|
|
928
|
+
}) as UseSuspenseQueriesTupleResult<T>
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
interface FamilyInternals<TItem> {
|
|
932
|
+
__queryFor: (item: TItem) => {
|
|
933
|
+
queryKey: ReadonlyArray<unknown>
|
|
934
|
+
queryFn?: unknown
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function useSuspenseQueriesFamily<TItem, TData, TError>(
|
|
939
|
+
family: QueriesResult<TItem, TData, TError>,
|
|
940
|
+
): ReadonlyArray<UseSuspenseQueryResult<TData, TError>> {
|
|
941
|
+
const mount = useUnit(family.mounted)
|
|
942
|
+
const unmount = useUnit(family.unmounted)
|
|
943
|
+
const refreshOne = useUnit(family.refreshOne)
|
|
944
|
+
React.useEffect(() => {
|
|
945
|
+
mount()
|
|
946
|
+
return () => unmount()
|
|
947
|
+
}, [mount, unmount])
|
|
948
|
+
|
|
949
|
+
const items = useUnit(family.$items)
|
|
950
|
+
const qc = useUnit(family.$queryClient)
|
|
951
|
+
|
|
952
|
+
// Error wins over pending.
|
|
953
|
+
for (const it of items) {
|
|
954
|
+
if (it.status === 'error') throw it.error
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const pending = items.filter((it) => it.status === 'pending')
|
|
958
|
+
if (pending.length > 0) {
|
|
959
|
+
if (!qc) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
'[@effector-tanstack-query/react] useSuspenseQueries: no QueryClient is set. ' +
|
|
962
|
+
'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',
|
|
963
|
+
)
|
|
964
|
+
}
|
|
965
|
+
const queryFor = (family as unknown as FamilyInternals<TItem>).__queryFor
|
|
966
|
+
throw Promise.all(
|
|
967
|
+
pending.map((it) =>
|
|
968
|
+
qc.fetchQuery(queryFor(it.source) as any).catch(() => undefined),
|
|
969
|
+
),
|
|
970
|
+
)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return items.map((it) => ({
|
|
974
|
+
data: it.data as TData,
|
|
975
|
+
error: it.error,
|
|
976
|
+
status: 'success' as const,
|
|
977
|
+
isPending: false as const,
|
|
978
|
+
isSuccess: true as const,
|
|
979
|
+
isError: false as const,
|
|
980
|
+
isFetching: it.isFetching,
|
|
981
|
+
isPlaceholderData: it.isPlaceholderData,
|
|
982
|
+
fetchStatus: it.fetchStatus,
|
|
983
|
+
refresh: () => refreshOne(it.source),
|
|
984
|
+
}))
|
|
985
|
+
}
|