@tanstack/query-core
Version:
The framework agnostic core that powers TanStack Query
258 lines (240 loc) • 8.09 kB
text/typescript
import { QueryObserver } from './queryObserver'
import { hasNextPage, hasPreviousPage } from './infiniteQueryBehavior'
import type { Subscribable } from './subscribable'
import type {
DefaultError,
DefaultedInfiniteQueryObserverOptions,
FetchNextPageOptions,
FetchPreviousPageOptions,
InfiniteData,
InfiniteQueryObserverBaseResult,
InfiniteQueryObserverOptions,
InfiniteQueryObserverResult,
QueryKey,
} from './types'
import type { QueryClient } from './queryClient'
import type { Query } from './query'
type InfiniteQueryObserverListener<TData, TError> = (
result: InfiniteQueryObserverResult<TData, TError>,
) => void
/**
* An `InfiniteQueryObserver` extends `QueryObserver` to observe and switch
* between infinite queries. It augments the base `QueryObserverResult` with
* infinite-query-specific fields and methods, such as `hasNextPage` and
* `fetchNextPage`, and is the primitive that framework adapters (e.g.
* `useInfiniteQuery`) build their hooks on top of.
* @example
* ```ts
* const observer = new InfiniteQueryObserver(queryClient, {
* queryKey: ['projects'],
* queryFn: ({ pageParam }) => fetchProjects(pageParam),
* initialPageParam: 0,
* getNextPageParam: (lastPage) => lastPage.nextCursor,
* })
*
* const unsubscribe = observer.subscribe((result) => console.log(result))
* ```
*/
export class InfiniteQueryObserver<
TQueryFnData = unknown,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> extends QueryObserver<
TQueryFnData,
TError,
TData,
InfiniteData<TQueryFnData, TPageParam>,
TQueryKey
> {
// Type override
override subscribe!: Subscribable<
InfiniteQueryObserverListener<TData, TError>
>['subscribe']
// Type override
override getCurrentResult!: ReplaceReturnType<
QueryObserver<
TQueryFnData,
TError,
TData,
InfiniteData<TQueryFnData, TPageParam>,
TQueryKey
>['getCurrentResult'],
InfiniteQueryObserverResult<TData, TError>
>
// Type override
protected override fetch!: ReplaceReturnType<
QueryObserver<
TQueryFnData,
TError,
TData,
InfiniteData<TQueryFnData, TPageParam>,
TQueryKey
>['fetch'],
Promise<InfiniteQueryObserverResult<TData, TError>>
>
constructor(
client: QueryClient,
options: InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
) {
super(client, options)
}
protected override bindMethods(): void {
super.bindMethods()
this.fetchNextPage = this.fetchNextPage.bind(this)
this.fetchPreviousPage = this.fetchPreviousPage.bind(this)
}
/**
* Updates the observer's options. Behaves the same as
* `QueryObserver.setOptions`, additionally marking the options as
* belonging to an infinite query before delegating to the base
* implementation.
* @param options - The new infinite query observer options.
*/
override setOptions(
options: InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): void {
options._type = 'infinite'
super.setOptions(options)
}
/**
* The infinite-query counterpart of {@link QueryObserver#getOptimisticResult}, marking the
* options as an infinite query before delegating to it. Called by framework adapters (e.g.
* `useInfiniteQuery`) ahead of subscribing, to compute the current `InfiniteQueryObserverResult`
* synchronously.
* @param options - The defaulted infinite query observer options to compute the result for.
* @returns The result for the given options.
*/
override getOptimisticResult(
options: DefaultedInfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): InfiniteQueryObserverResult<TData, TError> {
options._type = 'infinite'
return super.getOptimisticResult(options) as InfiniteQueryObserverResult<
TData,
TError
>
}
/**
* Fetches the next page of the infinite query and returns a promise that
* resolves with the resulting `InfiniteQueryObserverResult`. The page
* param used for the fetch is determined by `getNextPageParam`, which
* receives the current pages/page params and whose result also determines
* `hasNextPage`.
* @param options - Set `cancelRefetch` to `false` to ignore the call while a fetch is running,
* and `throwOnError` to `true` to reject when the fetch fails.
* @returns A promise that resolves with the result after the next page is fetched. With
* `cancelRefetch: false`, a running fetch is reused instead, so the next page may not be fetched.
* @see {@link InfiniteQueryObserver#fetchPreviousPage}
* @example
* ```ts
* const { hasNextPage } = observer.getCurrentResult()
*
* if (hasNextPage) {
* await observer.fetchNextPage()
* }
* ```
*/
fetchNextPage(
options?: FetchNextPageOptions,
): Promise<InfiniteQueryObserverResult<TData, TError>> {
return this.fetch({
...options,
meta: {
fetchMore: { direction: 'forward' },
},
})
}
/**
* Fetches the previous page of the infinite query and returns a promise
* that resolves with the resulting `InfiniteQueryObserverResult`. The page
* param used for the fetch is determined by `getPreviousPageParam`, which
* receives the current pages/page params and whose result also determines
* `hasPreviousPage`.
* @param options - Set `cancelRefetch` to `false` to ignore the call while a fetch is running,
* and `throwOnError` to `true` to reject when the fetch fails.
* @returns A promise that resolves with the result after the previous page is fetched. With
* `cancelRefetch: false`, a running fetch is reused instead, so the previous page may not be fetched.
* @see {@link InfiniteQueryObserver#fetchNextPage}
* @example
* ```ts
* const { hasPreviousPage } = observer.getCurrentResult()
*
* if (hasPreviousPage) {
* await observer.fetchPreviousPage()
* }
* ```
*/
fetchPreviousPage(
options?: FetchPreviousPageOptions,
): Promise<InfiniteQueryObserverResult<TData, TError>> {
return this.fetch({
...options,
meta: {
fetchMore: { direction: 'backward' },
},
})
}
protected override createResult(
query: Query<
TQueryFnData,
TError,
InfiniteData<TQueryFnData, TPageParam>,
TQueryKey
>,
options: InfiniteQueryObserverOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): InfiniteQueryObserverResult<TData, TError> {
const { state } = query
const parentResult = super.createResult(query, options)
const { isFetching, isRefetching, isError, isRefetchError } = parentResult
const fetchDirection = state.fetchMeta?.fetchMore?.direction
const isFetchNextPageError = isError && fetchDirection === 'forward'
const isFetchingNextPage = isFetching && fetchDirection === 'forward'
const isFetchPreviousPageError = isError && fetchDirection === 'backward'
const isFetchingPreviousPage = isFetching && fetchDirection === 'backward'
const result: InfiniteQueryObserverBaseResult<TData, TError> = {
...parentResult,
fetchNextPage: this.fetchNextPage,
fetchPreviousPage: this.fetchPreviousPage,
hasNextPage: hasNextPage(options, state.data),
hasPreviousPage: hasPreviousPage(options, state.data),
isFetchNextPageError,
isFetchingNextPage,
isFetchPreviousPageError,
isFetchingPreviousPage,
isRefetchError:
isRefetchError && !isFetchNextPageError && !isFetchPreviousPageError,
isRefetching:
isRefetching && !isFetchingNextPage && !isFetchingPreviousPage,
}
return result as InfiniteQueryObserverResult<TData, TError>
}
}
type ReplaceReturnType<
TFunction extends (...args: Array<any>) => unknown,
TReturn,
> = (...args: Parameters<TFunction>) => TReturn