|
'use client' |
|
import * as React from 'react' |
|
|
|
import { |
|
QueriesObserver, |
|
QueryObserver, |
|
notifyManager, |
|
} from '@tanstack/query-core' |
|
import { useQueryClient } from './QueryClientProvider' |
|
import { useIsRestoring } from './isRestoring' |
|
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary' |
|
import { |
|
ensurePreventErrorBoundaryRetry, |
|
getHasError, |
|
useClearResetErrorBoundary, |
|
} from './errorBoundaryUtils' |
|
import { |
|
ensureSuspenseTimers, |
|
fetchOptimistic, |
|
shouldSuspend, |
|
willFetch, |
|
} from './suspense' |
|
import { noop } from './utils' |
|
import type { |
|
DefinedUseQueryResult, |
|
UseQueryOptions, |
|
UseQueryResult, |
|
} from './types' |
|
import type { |
|
DefaultError, |
|
OmitKeyof, |
|
QueriesObserverOptions, |
|
QueriesPlaceholderDataFunction, |
|
QueryClient, |
|
QueryFunction, |
|
QueryKey, |
|
QueryObserverOptions, |
|
ThrowOnError, |
|
} from '@tanstack/query-core' |
|
|
|
|
|
|
|
type UseQueryOptionsForUseQueries< |
|
TQueryFnData = unknown, |
|
TError = DefaultError, |
|
TData = TQueryFnData, |
|
TQueryKey extends QueryKey = QueryKey, |
|
> = OmitKeyof< |
|
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, |
|
'placeholderData' |
|
> & { |
|
placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction<TQueryFnData> |
|
} |
|
|
|
|
|
type MAXIMUM_DEPTH = 20 |
|
|
|
|
|
type SkipTokenForUseQueries = symbol |
|
|
|
type GetUseQueryOptionsForUseQueries<T> = |
|
|
|
T extends { |
|
queryFnData: infer TQueryFnData |
|
error?: infer TError |
|
data: infer TData |
|
} |
|
? UseQueryOptionsForUseQueries<TQueryFnData, TError, TData> |
|
: T extends { queryFnData: infer TQueryFnData; error?: infer TError } |
|
? UseQueryOptionsForUseQueries<TQueryFnData, TError> |
|
: T extends { data: infer TData; error?: infer TError } |
|
? UseQueryOptionsForUseQueries<unknown, TError, TData> |
|
: |
|
T extends [infer TQueryFnData, infer TError, infer TData] |
|
? UseQueryOptionsForUseQueries<TQueryFnData, TError, TData> |
|
: T extends [infer TQueryFnData, infer TError] |
|
? UseQueryOptionsForUseQueries<TQueryFnData, TError> |
|
: T extends [infer TQueryFnData] |
|
? UseQueryOptionsForUseQueries<TQueryFnData> |
|
: |
|
T extends { |
|
queryFn?: |
|
| QueryFunction<infer TQueryFnData, infer TQueryKey> |
|
| SkipTokenForUseQueries |
|
select?: (data: any) => infer TData |
|
throwOnError?: ThrowOnError<any, infer TError, any, any> |
|
} |
|
? UseQueryOptionsForUseQueries< |
|
TQueryFnData, |
|
unknown extends TError ? DefaultError : TError, |
|
unknown extends TData ? TQueryFnData : TData, |
|
TQueryKey |
|
> |
|
: |
|
UseQueryOptionsForUseQueries |
|
|
|
|
|
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends { |
|
initialData?: infer TInitialData |
|
} |
|
? unknown extends TInitialData |
|
? UseQueryResult<TData, TError> |
|
: TInitialData extends TData |
|
? DefinedUseQueryResult<TData, TError> |
|
: TInitialData extends () => infer TInitialDataResult |
|
? unknown extends TInitialDataResult |
|
? UseQueryResult<TData, TError> |
|
: TInitialDataResult extends TData |
|
? DefinedUseQueryResult<TData, TError> |
|
: UseQueryResult<TData, TError> |
|
: UseQueryResult<TData, TError> |
|
: UseQueryResult<TData, TError> |
|
|
|
type GetUseQueryResult<T> = |
|
|
|
T extends { queryFnData: any; error?: infer TError; data: infer TData } |
|
? GetDefinedOrUndefinedQueryResult<T, TData, TError> |
|
: T extends { queryFnData: infer TQueryFnData; error?: infer TError } |
|
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError> |
|
: T extends { data: infer TData; error?: infer TError } |
|
? GetDefinedOrUndefinedQueryResult<T, TData, TError> |
|
: |
|
T extends [any, infer TError, infer TData] |
|
? GetDefinedOrUndefinedQueryResult<T, TData, TError> |
|
: T extends [infer TQueryFnData, infer TError] |
|
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError> |
|
: T extends [infer TQueryFnData] |
|
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData> |
|
: |
|
T extends { |
|
queryFn?: |
|
| QueryFunction<infer TQueryFnData, any> |
|
| SkipTokenForUseQueries |
|
select?: (data: any) => infer TData |
|
throwOnError?: ThrowOnError<any, infer TError, any, any> |
|
} |
|
? GetDefinedOrUndefinedQueryResult< |
|
T, |
|
unknown extends TData ? TQueryFnData : TData, |
|
unknown extends TError ? DefaultError : TError |
|
> |
|
: |
|
UseQueryResult |
|
|
|
|
|
|
|
|
|
export type QueriesOptions< |
|
T extends Array<any>, |
|
TResults extends Array<any> = [], |
|
TDepth extends ReadonlyArray<number> = [], |
|
> = TDepth['length'] extends MAXIMUM_DEPTH |
|
? Array<UseQueryOptionsForUseQueries> |
|
: T extends [] |
|
? [] |
|
: T extends [infer Head] |
|
? [...TResults, GetUseQueryOptionsForUseQueries<Head>] |
|
: T extends [infer Head, ...infer Tails] |
|
? QueriesOptions< |
|
[...Tails], |
|
[...TResults, GetUseQueryOptionsForUseQueries<Head>], |
|
[...TDepth, 1] |
|
> |
|
: ReadonlyArray<unknown> extends T |
|
? T |
|
: |
|
|
|
T extends Array< |
|
UseQueryOptionsForUseQueries< |
|
infer TQueryFnData, |
|
infer TError, |
|
infer TData, |
|
infer TQueryKey |
|
> |
|
> |
|
? Array< |
|
UseQueryOptionsForUseQueries< |
|
TQueryFnData, |
|
TError, |
|
TData, |
|
TQueryKey |
|
> |
|
> |
|
: |
|
Array<UseQueryOptionsForUseQueries> |
|
|
|
|
|
|
|
|
|
export type QueriesResults< |
|
T extends Array<any>, |
|
TResults extends Array<any> = [], |
|
TDepth extends ReadonlyArray<number> = [], |
|
> = TDepth['length'] extends MAXIMUM_DEPTH |
|
? Array<UseQueryResult> |
|
: T extends [] |
|
? [] |
|
: T extends [infer Head] |
|
? [...TResults, GetUseQueryResult<Head>] |
|
: T extends [infer Head, ...infer Tails] |
|
? QueriesResults< |
|
[...Tails], |
|
[...TResults, GetUseQueryResult<Head>], |
|
[...TDepth, 1] |
|
> |
|
: T extends Array< |
|
UseQueryOptionsForUseQueries< |
|
infer TQueryFnData, |
|
infer TError, |
|
infer TData, |
|
any |
|
> |
|
> |
|
? |
|
Array< |
|
UseQueryResult< |
|
unknown extends TData ? TQueryFnData : TData, |
|
unknown extends TError ? DefaultError : TError |
|
> |
|
> |
|
: |
|
Array<UseQueryResult> |
|
|
|
export function useQueries< |
|
T extends Array<any>, |
|
TCombinedResult = QueriesResults<T>, |
|
>( |
|
{ |
|
queries, |
|
...options |
|
}: { |
|
queries: readonly [...QueriesOptions<T>] |
|
combine?: (result: QueriesResults<T>) => TCombinedResult |
|
}, |
|
queryClient?: QueryClient, |
|
): TCombinedResult { |
|
const client = useQueryClient(queryClient) |
|
const isRestoring = useIsRestoring() |
|
const errorResetBoundary = useQueryErrorResetBoundary() |
|
|
|
const defaultedQueries = React.useMemo( |
|
() => |
|
queries.map((opts) => { |
|
const defaultedOptions = client.defaultQueryOptions( |
|
opts as QueryObserverOptions, |
|
) |
|
|
|
|
|
defaultedOptions._optimisticResults = isRestoring |
|
? 'isRestoring' |
|
: 'optimistic' |
|
|
|
return defaultedOptions |
|
}), |
|
[queries, client, isRestoring], |
|
) |
|
|
|
defaultedQueries.forEach((query) => { |
|
ensureSuspenseTimers(query) |
|
ensurePreventErrorBoundaryRetry(query, errorResetBoundary) |
|
}) |
|
|
|
useClearResetErrorBoundary(errorResetBoundary) |
|
|
|
const [observer] = React.useState( |
|
() => |
|
new QueriesObserver<TCombinedResult>( |
|
client, |
|
defaultedQueries, |
|
options as QueriesObserverOptions<TCombinedResult>, |
|
), |
|
) |
|
|
|
const [optimisticResult, getCombinedResult, trackResult] = |
|
observer.getOptimisticResult( |
|
defaultedQueries, |
|
(options as QueriesObserverOptions<TCombinedResult>).combine, |
|
) |
|
|
|
React.useSyncExternalStore( |
|
React.useCallback( |
|
(onStoreChange) => |
|
isRestoring |
|
? noop |
|
: observer.subscribe(notifyManager.batchCalls(onStoreChange)), |
|
[observer, isRestoring], |
|
), |
|
() => observer.getCurrentResult(), |
|
() => observer.getCurrentResult(), |
|
) |
|
|
|
React.useEffect(() => { |
|
|
|
|
|
observer.setQueries( |
|
defaultedQueries, |
|
options as QueriesObserverOptions<TCombinedResult>, |
|
{ |
|
listeners: false, |
|
}, |
|
) |
|
}, [defaultedQueries, options, observer]) |
|
|
|
const shouldAtLeastOneSuspend = optimisticResult.some((result, index) => |
|
shouldSuspend(defaultedQueries[index], result), |
|
) |
|
|
|
const suspensePromises = shouldAtLeastOneSuspend |
|
? optimisticResult.flatMap((result, index) => { |
|
const opts = defaultedQueries[index] |
|
|
|
if (opts) { |
|
const queryObserver = new QueryObserver(client, opts) |
|
if (shouldSuspend(opts, result)) { |
|
return fetchOptimistic(opts, queryObserver, errorResetBoundary) |
|
} else if (willFetch(result, isRestoring)) { |
|
void fetchOptimistic(opts, queryObserver, errorResetBoundary) |
|
} |
|
} |
|
return [] |
|
}) |
|
: [] |
|
|
|
if (suspensePromises.length > 0) { |
|
throw Promise.all(suspensePromises) |
|
} |
|
const firstSingleResultWhichShouldThrow = optimisticResult.find( |
|
(result, index) => { |
|
const query = defaultedQueries[index] |
|
return ( |
|
query && |
|
getHasError({ |
|
result, |
|
errorResetBoundary, |
|
throwOnError: query.throwOnError, |
|
query: client.getQueryCache().get(query.queryHash), |
|
}) |
|
) |
|
}, |
|
) |
|
|
|
if (firstSingleResultWhichShouldThrow?.error) { |
|
throw firstSingleResultWhichShouldThrow.error |
|
} |
|
|
|
return getCombinedResult(trackResult()) |
|
} |
|
|