New source found from dndbeyond.com
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
const IsRestoringContext = React.createContext(false)
|
||||
|
||||
export const useIsRestoring = () => React.useContext(IsRestoringContext)
|
||||
export const IsRestoringProvider = IsRestoringContext.Provider
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
import type { QueryClient } from '@tanstack/query-core'
|
||||
|
||||
export const QueryClientContext = React.createContext<QueryClient | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
export const useQueryClient = (queryClient?: QueryClient) => {
|
||||
const client = React.useContext(QueryClientContext)
|
||||
|
||||
if (queryClient) {
|
||||
return queryClient
|
||||
}
|
||||
|
||||
if (!client) {
|
||||
throw new Error('No QueryClient set, use QueryClientProvider to set one')
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
export type QueryClientProviderProps = {
|
||||
client: QueryClient
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const QueryClientProvider = ({
|
||||
client,
|
||||
children,
|
||||
}: QueryClientProviderProps): React.JSX.Element => {
|
||||
React.useEffect(() => {
|
||||
client.mount()
|
||||
return () => {
|
||||
client.unmount()
|
||||
}
|
||||
}, [client])
|
||||
|
||||
return (
|
||||
<QueryClientContext.Provider value={client}>
|
||||
{children}
|
||||
</QueryClientContext.Provider>
|
||||
)
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
// CONTEXT
|
||||
export type QueryErrorResetFunction = () => void
|
||||
export type QueryErrorIsResetFunction = () => boolean
|
||||
export type QueryErrorClearResetFunction = () => void
|
||||
|
||||
export interface QueryErrorResetBoundaryValue {
|
||||
clearReset: QueryErrorClearResetFunction
|
||||
isReset: QueryErrorIsResetFunction
|
||||
reset: QueryErrorResetFunction
|
||||
}
|
||||
|
||||
function createValue(): QueryErrorResetBoundaryValue {
|
||||
let isReset = false
|
||||
return {
|
||||
clearReset: () => {
|
||||
isReset = false
|
||||
},
|
||||
reset: () => {
|
||||
isReset = true
|
||||
},
|
||||
isReset: () => {
|
||||
return isReset
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const QueryErrorResetBoundaryContext = React.createContext(createValue())
|
||||
|
||||
// HOOK
|
||||
|
||||
export const useQueryErrorResetBoundary = () =>
|
||||
React.useContext(QueryErrorResetBoundaryContext)
|
||||
|
||||
// COMPONENT
|
||||
|
||||
export type QueryErrorResetBoundaryFunction = (
|
||||
value: QueryErrorResetBoundaryValue,
|
||||
) => React.ReactNode
|
||||
|
||||
export interface QueryErrorResetBoundaryProps {
|
||||
children: QueryErrorResetBoundaryFunction | React.ReactNode
|
||||
}
|
||||
|
||||
export const QueryErrorResetBoundary = ({
|
||||
children,
|
||||
}: QueryErrorResetBoundaryProps) => {
|
||||
const [value] = React.useState(() => createValue())
|
||||
return (
|
||||
<QueryErrorResetBoundaryContext.Provider value={value}>
|
||||
{typeof children === 'function' ? children(value) : children}
|
||||
</QueryErrorResetBoundaryContext.Provider>
|
||||
)
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import { shouldThrowError } from '@tanstack/query-core'
|
||||
import type {
|
||||
DefaultedQueryObserverOptions,
|
||||
Query,
|
||||
QueryKey,
|
||||
QueryObserverResult,
|
||||
ThrowOnError,
|
||||
} from '@tanstack/query-core'
|
||||
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
|
||||
|
||||
export const ensurePreventErrorBoundaryRetry = <
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey extends QueryKey,
|
||||
>(
|
||||
options: DefaultedQueryObserverOptions<
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey
|
||||
>,
|
||||
errorResetBoundary: QueryErrorResetBoundaryValue,
|
||||
query: Query<TQueryFnData, TError, TQueryData, TQueryKey> | undefined,
|
||||
) => {
|
||||
const throwOnError =
|
||||
query?.state.error && typeof options.throwOnError === 'function'
|
||||
? shouldThrowError(options.throwOnError, [query.state.error, query])
|
||||
: options.throwOnError
|
||||
|
||||
if (
|
||||
options.suspense ||
|
||||
options.experimental_prefetchInRender ||
|
||||
throwOnError
|
||||
) {
|
||||
// Prevent retrying failed query if the error boundary has not been reset yet
|
||||
if (!errorResetBoundary.isReset()) {
|
||||
options.retryOnMount = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const useClearResetErrorBoundary = (
|
||||
errorResetBoundary: QueryErrorResetBoundaryValue,
|
||||
) => {
|
||||
React.useEffect(() => {
|
||||
errorResetBoundary.clearReset()
|
||||
}, [errorResetBoundary])
|
||||
}
|
||||
|
||||
export const getHasError = <
|
||||
TData,
|
||||
TError,
|
||||
TQueryFnData,
|
||||
TQueryData,
|
||||
TQueryKey extends QueryKey,
|
||||
>({
|
||||
result,
|
||||
errorResetBoundary,
|
||||
throwOnError,
|
||||
query,
|
||||
suspense,
|
||||
}: {
|
||||
result: QueryObserverResult<TData, TError>
|
||||
errorResetBoundary: QueryErrorResetBoundaryValue
|
||||
throwOnError: ThrowOnError<TQueryFnData, TError, TQueryData, TQueryKey>
|
||||
query: Query<TQueryFnData, TError, TQueryData, TQueryKey> | undefined
|
||||
suspense: boolean | undefined
|
||||
}) => {
|
||||
return (
|
||||
result.isError &&
|
||||
!errorResetBoundary.isReset() &&
|
||||
!result.isFetching &&
|
||||
query &&
|
||||
((suspense && result.data === undefined) ||
|
||||
shouldThrowError(throwOnError, [result.error, query]))
|
||||
)
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
DefaultError,
|
||||
DefaultedQueryObserverOptions,
|
||||
Query,
|
||||
QueryKey,
|
||||
QueryObserver,
|
||||
QueryObserverResult,
|
||||
} from '@tanstack/query-core'
|
||||
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
|
||||
|
||||
export const defaultThrowOnError = <
|
||||
TQueryFnData = unknown,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
_error: TError,
|
||||
query: Query<TQueryFnData, TError, TData, TQueryKey>,
|
||||
) => query.state.data === undefined
|
||||
|
||||
export const ensureSuspenseTimers = (
|
||||
defaultedOptions: DefaultedQueryObserverOptions<any, any, any, any, any>,
|
||||
) => {
|
||||
if (defaultedOptions.suspense) {
|
||||
// Handle staleTime to ensure minimum 1000ms in Suspense mode
|
||||
// This prevents unnecessary refetching when components remount after suspending
|
||||
const MIN_SUSPENSE_TIME_MS = 1000
|
||||
|
||||
const clamp = (value: number | 'static' | undefined) =>
|
||||
value === 'static'
|
||||
? value
|
||||
: Math.max(value ?? MIN_SUSPENSE_TIME_MS, MIN_SUSPENSE_TIME_MS)
|
||||
|
||||
const originalStaleTime = defaultedOptions.staleTime
|
||||
defaultedOptions.staleTime =
|
||||
typeof originalStaleTime === 'function'
|
||||
? (...args) => clamp(originalStaleTime(...args))
|
||||
: clamp(originalStaleTime)
|
||||
|
||||
if (typeof defaultedOptions.gcTime === 'number') {
|
||||
defaultedOptions.gcTime = Math.max(
|
||||
defaultedOptions.gcTime,
|
||||
MIN_SUSPENSE_TIME_MS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const willFetch = (
|
||||
result: QueryObserverResult<any, any>,
|
||||
isRestoring: boolean,
|
||||
) => result.isLoading && result.isFetching && !isRestoring
|
||||
|
||||
export const shouldSuspend = (
|
||||
defaultedOptions:
|
||||
| DefaultedQueryObserverOptions<any, any, any, any, any>
|
||||
| undefined,
|
||||
result: QueryObserverResult<any, any>,
|
||||
) => defaultedOptions?.suspense && result.isPending
|
||||
|
||||
export const fetchOptimistic = <
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey extends QueryKey,
|
||||
>(
|
||||
defaultedOptions: DefaultedQueryObserverOptions<
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey
|
||||
>,
|
||||
observer: QueryObserver<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
|
||||
errorResetBoundary: QueryErrorResetBoundaryValue,
|
||||
) =>
|
||||
observer.fetchOptimistic(defaultedOptions).catch(() => {
|
||||
errorResetBoundary.clearReset()
|
||||
})
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
import { isServer, noop, notifyManager } from '@tanstack/query-core'
|
||||
import { useQueryClient } from './QueryClientProvider'
|
||||
import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
|
||||
import {
|
||||
ensurePreventErrorBoundaryRetry,
|
||||
getHasError,
|
||||
useClearResetErrorBoundary,
|
||||
} from './errorBoundaryUtils'
|
||||
import { useIsRestoring } from './IsRestoringProvider'
|
||||
import {
|
||||
ensureSuspenseTimers,
|
||||
fetchOptimistic,
|
||||
shouldSuspend,
|
||||
willFetch,
|
||||
} from './suspense'
|
||||
import type {
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
QueryObserver,
|
||||
QueryObserverResult,
|
||||
} from '@tanstack/query-core'
|
||||
import type { UseBaseQueryOptions } from './types'
|
||||
|
||||
export function useBaseQuery<
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey extends QueryKey,
|
||||
>(
|
||||
options: UseBaseQueryOptions<
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TData,
|
||||
TQueryData,
|
||||
TQueryKey
|
||||
>,
|
||||
Observer: typeof QueryObserver,
|
||||
queryClient?: QueryClient,
|
||||
): QueryObserverResult<TData, TError> {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof options !== 'object' || Array.isArray(options)) {
|
||||
throw new Error(
|
||||
'Bad argument type. Starting with v5, only the "Object" form is allowed when calling query related functions. Please use the error stack to find the culprit call. More info here: https://tanstack.com/query/latest/docs/react/guides/migrating-to-v5#supports-a-single-signature-one-object',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const isRestoring = useIsRestoring()
|
||||
const errorResetBoundary = useQueryErrorResetBoundary()
|
||||
const client = useQueryClient(queryClient)
|
||||
const defaultedOptions = client.defaultQueryOptions(options)
|
||||
;(client.getDefaultOptions().queries as any)?._experimental_beforeQuery?.(
|
||||
defaultedOptions,
|
||||
)
|
||||
|
||||
const query = client
|
||||
.getQueryCache()
|
||||
.get<
|
||||
TQueryFnData,
|
||||
TError,
|
||||
TQueryData,
|
||||
TQueryKey
|
||||
>(defaultedOptions.queryHash)
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!defaultedOptions.queryFn) {
|
||||
console.error(
|
||||
`[${defaultedOptions.queryHash}]: No queryFn was passed as an option, and no default queryFn was found. The queryFn parameter is only optional when using a default queryFn. More info here: https://tanstack.com/query/latest/docs/framework/react/guides/default-query-function`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure results are optimistically set in fetching state before subscribing or updating options
|
||||
defaultedOptions._optimisticResults = isRestoring
|
||||
? 'isRestoring'
|
||||
: 'optimistic'
|
||||
|
||||
ensureSuspenseTimers(defaultedOptions)
|
||||
ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary, query)
|
||||
useClearResetErrorBoundary(errorResetBoundary)
|
||||
|
||||
// this needs to be invoked before creating the Observer because that can create a cache entry
|
||||
const isNewCacheEntry = !client
|
||||
.getQueryCache()
|
||||
.get(defaultedOptions.queryHash)
|
||||
|
||||
const [observer] = React.useState(
|
||||
() =>
|
||||
new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(
|
||||
client,
|
||||
defaultedOptions,
|
||||
),
|
||||
)
|
||||
|
||||
// note: this must be called before useSyncExternalStore
|
||||
const result = observer.getOptimisticResult(defaultedOptions)
|
||||
|
||||
const shouldSubscribe = !isRestoring && options.subscribed !== false
|
||||
React.useSyncExternalStore(
|
||||
React.useCallback(
|
||||
(onStoreChange) => {
|
||||
const unsubscribe = shouldSubscribe
|
||||
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
|
||||
: noop
|
||||
|
||||
// Update result to make sure we did not miss any query updates
|
||||
// between creating the observer and subscribing to it.
|
||||
observer.updateResult()
|
||||
|
||||
return unsubscribe
|
||||
},
|
||||
[observer, shouldSubscribe],
|
||||
),
|
||||
() => observer.getCurrentResult(),
|
||||
() => observer.getCurrentResult(),
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
observer.setOptions(defaultedOptions)
|
||||
}, [defaultedOptions, observer])
|
||||
|
||||
// Handle suspense
|
||||
if (shouldSuspend(defaultedOptions, result)) {
|
||||
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
|
||||
}
|
||||
|
||||
// Handle error boundary
|
||||
if (
|
||||
getHasError({
|
||||
result,
|
||||
errorResetBoundary,
|
||||
throwOnError: defaultedOptions.throwOnError,
|
||||
query,
|
||||
suspense: defaultedOptions.suspense,
|
||||
})
|
||||
) {
|
||||
throw result.error
|
||||
}
|
||||
|
||||
;(client.getDefaultOptions().queries as any)?._experimental_afterQuery?.(
|
||||
defaultedOptions,
|
||||
result,
|
||||
)
|
||||
|
||||
if (
|
||||
defaultedOptions.experimental_prefetchInRender &&
|
||||
!isServer &&
|
||||
willFetch(result, isRestoring)
|
||||
) {
|
||||
const promise = isNewCacheEntry
|
||||
? // Fetch immediately on render in order to ensure `.promise` is resolved even if the component is unmounted
|
||||
fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
|
||||
: // subscribe to the "cache promise" so that we can finalize the currentThenable once data comes in
|
||||
query?.promise
|
||||
|
||||
promise?.catch(noop).finally(() => {
|
||||
// `.updateResult()` will trigger `.#currentThenable` to finalize
|
||||
observer.updateResult()
|
||||
})
|
||||
}
|
||||
|
||||
// Handle result property usage tracking
|
||||
return !defaultedOptions.notifyOnChangeProps
|
||||
? observer.trackResult(result)
|
||||
: result
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
'use client'
|
||||
import { QueryObserver } from '@tanstack/query-core'
|
||||
import { useBaseQuery } from './useBaseQuery'
|
||||
import type {
|
||||
DefaultError,
|
||||
NoInfer,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
} from '@tanstack/query-core'
|
||||
import type {
|
||||
DefinedUseQueryResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from './types'
|
||||
import type {
|
||||
DefinedInitialDataOptions,
|
||||
UndefinedInitialDataOptions,
|
||||
} from './queryOptions'
|
||||
|
||||
export function useQuery<
|
||||
TQueryFnData = unknown,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<NoInfer<TData>, TError>
|
||||
|
||||
export function useQuery<
|
||||
TQueryFnData = unknown,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<NoInfer<TData>, TError>
|
||||
|
||||
export function useQuery<
|
||||
TQueryFnData = unknown,
|
||||
TError = DefaultError,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey,
|
||||
>(
|
||||
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<NoInfer<TData>, TError>
|
||||
|
||||
export function useQuery(options: UseQueryOptions, queryClient?: QueryClient) {
|
||||
return useBaseQuery(options, QueryObserver, queryClient)
|
||||
}
|
||||
Reference in New Issue
Block a user