import { addToEnd, addToStart, ensureQueryFn } from './utils' import type { QueryBehavior } from './query' import type { InfiniteData, InfiniteQueryPageParamsOptions, OmitKeyof, QueryFunctionContext, QueryKey, } from './types' export function infiniteQueryBehavior( pages?: number, ): QueryBehavior> { return { onFetch: (context, query) => { const options = context.options as InfiniteQueryPageParamsOptions const direction = context.fetchOptions?.meta?.fetchMore?.direction const oldPages = context.state.data?.pages || [] const oldPageParams = context.state.data?.pageParams || [] let result: InfiniteData = { pages: [], pageParams: [] } let currentPage = 0 const fetchFn = async () => { let cancelled = false const addSignalProperty = (object: unknown) => { Object.defineProperty(object, 'signal', { enumerable: true, get: () => { if (context.signal.aborted) { cancelled = true } else { context.signal.addEventListener('abort', () => { cancelled = true }) } return context.signal }, }) } const queryFn = ensureQueryFn(context.options, context.fetchOptions) // Create function to fetch a page const fetchPage = async ( data: InfiniteData, param: unknown, previous?: boolean, ): Promise> => { if (cancelled) { return Promise.reject() } if (param == null && data.pages.length) { return Promise.resolve(data) } const createQueryFnContext = () => { const queryFnContext: OmitKeyof< QueryFunctionContext, 'signal' > = { client: context.client, queryKey: context.queryKey, pageParam: param, direction: previous ? 'backward' : 'forward', meta: context.options.meta, } addSignalProperty(queryFnContext) return queryFnContext as QueryFunctionContext } const queryFnContext = createQueryFnContext() const page = await queryFn(queryFnContext) const { maxPages } = context.options const addTo = previous ? addToStart : addToEnd return { pages: addTo(data.pages, page, maxPages), pageParams: addTo(data.pageParams, param, maxPages), } } // fetch next / previous page? if (direction && oldPages.length) { const previous = direction === 'backward' const pageParamFn = previous ? getPreviousPageParam : getNextPageParam const oldData = { pages: oldPages, pageParams: oldPageParams, } const param = pageParamFn(options, oldData) result = await fetchPage(oldData, param, previous) } else { const remainingPages = pages ?? oldPages.length // Fetch all pages do { const param = currentPage === 0 ? (oldPageParams[0] ?? options.initialPageParam) : getNextPageParam(options, result) if (currentPage > 0 && param == null) { break } result = await fetchPage(result, param) currentPage++ } while (currentPage < remainingPages) } return result } if (context.options.persister) { context.fetchFn = () => { return context.options.persister?.( fetchFn as any, { client: context.client, queryKey: context.queryKey, meta: context.options.meta, signal: context.signal, }, query, ) } } else { context.fetchFn = fetchFn } }, } } function getNextPageParam( options: InfiniteQueryPageParamsOptions, { pages, pageParams }: InfiniteData, ): unknown | undefined { const lastIndex = pages.length - 1 return pages.length > 0 ? options.getNextPageParam( pages[lastIndex], pages, pageParams[lastIndex], pageParams, ) : undefined } function getPreviousPageParam( options: InfiniteQueryPageParamsOptions, { pages, pageParams }: InfiniteData, ): unknown | undefined { return pages.length > 0 ? options.getPreviousPageParam?.(pages[0], pages, pageParams[0], pageParams) : undefined } /** * Checks if there is a next page. */ export function hasNextPage( options: InfiniteQueryPageParamsOptions, data?: InfiniteData, ): boolean { if (!data) return false return getNextPageParam(options, data) != null } /** * Checks if there is a previous page. */ export function hasPreviousPage( options: InfiniteQueryPageParamsOptions, data?: InfiniteData, ): boolean { if (!data || !options.getPreviousPageParam) return false return getPreviousPageParam(options, data) != null }