new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

TanStack Query · React · all subjects

infinite-queries

75 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

useInfiniteQuery return values for pagination

useInfiniteQuery returns the following pagination-related values: fetchNextPage and fetchPreviousPage functions (fetchNextPage is required), hasNextPage and hasPreviousPage booleans (true if getNextPageParam or getPreviousPageParam returns a value other than null or undefined), and isFetchingNextPage and isFetchingPreviousPage booleans to distinguish between background refresh state and loading more state.

useInfiniteQuery required options

useInfiniteQuery requires the following options: initialPageParam (specifies the initial page param), getNextPageParam (determines if there is more data to load and what information to fetch next), and queryFn (the fetching function that receives pageParam as a parameter).

getPreviousPageParam for bidirectional infinite lists

getPreviousPageParam is an optional function that receives the firstPage, pages array, and firstPageParam as parameters, and returns the page param for the previous page or null/undefined to indicate there is no previous page. Use it along with fetchPreviousPage and hasPreviousPage to implement bidirectional infinite lists.

useInfiniteQuery hook overview

useInfiniteQuery is a specialized version of useQuery for rendering lists that additively load more data or support infinite scroll patterns. It is the primary hook for implementing pagination and load-more UI patterns in TanStack Query.

initialData and placeholderData structure for infinite queries

When using initialData or placeholderData with useInfiniteQuery, the data must conform to the same structure as infinite query results, containing an object with pages and pageParams properties.

Concurrent fetch risk in infinite queries

Calling fetchNextPage while an ongoing fetch is in progress runs the risk of overwriting data refreshes happening in the background. There can only be a single ongoing fetch for an InfiniteQuery because a single cache entry is shared for all pages. Attempting to fetch twice simultaneously might lead to data overwrites.

cancelRefetch option in fetchNextPage

The fetchNextPage function accepts an optional object parameter with a cancelRefetch property (default: true). Setting cancelRefetch to false enables simultaneous fetching of pages.

Check isFetching before calling fetchNextPage

It is highly recommended to verify that the query is not in an isFetching state before calling fetchNextPage, especially if the user won't directly control that call. This prevents conflicts and data overwrites.

Infinite query refetch behavior

When an infinite query becomes stale and needs to be refetched, each group is fetched sequentially starting from the first one. This ensures that stale cursors are not used and prevents duplicates or skipped records. If an infinite query's results are removed from the queryCache, pagination restarts at the initial state with only the initial group being requested.

Manually remove first page from infinite query

To manually remove the first page from an infinite query, use queryClient.setQueryData to update the data by slicing pages and pageParams arrays starting from index 1.

Manually remove single value from infinite query page

To manually remove a single value from an individual page in an infinite query, use queryClient.setQueryData to map over pages and filter out the item by id, while keeping pageParams unchanged.

Keep only first page in infinite query

To keep only the first page in an infinite query, use queryClient.setQueryData to slice both pages and pageParams arrays to keep only the first element.

Data structure requirement when manually updating infinite queries

When manually updating an infinite query with queryClient.setQueryData, always maintain the same data structure with pages and pageParams properties.

maxPages option for limiting stored pages

Use the maxPages option in useInfiniteQuery to limit the number of pages stored in the query data. This improves performance and UX by reducing memory usage when users can load many pages and reducing network usage during refetch of infinite queries with many pages.

Using pageParam as cursor when API doesn't return cursor

If your API doesn't return a cursor, you can use pageParam as a cursor. Both getNextPageParam and getPreviousPageParam receive the pageParam of the current page as a parameter, allowing you to calculate the next or previous page param based on it.

Safe infinite query fetch with onEndReached check

Example: Safe pattern for calling fetchNextPage without user control, checking both hasNextPage and !isFetching before triggering the fetch to prevent concurrent requests. ```jsx <List onEndReached={() => hasNextPage && !isFetching && fetchNextPage()} /> ```

Bidirectional infinite list example

Example: Implementing a bidirectional infinite list using getPreviousPageParam and fetchPreviousPage along with getNextPageParam and fetchNextPage. ```tsx useInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor, }) ```

Reverse pages with select option example

Example: Using the select option to reverse the order of pages in an infinite query. ```tsx useInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, select: (data) => ({ pages: [...data.pages].reverse(), pageParams: [...data.pageParams].reverse(), }), }) ```

Manually remove first page example

Example: Manually removing the first page from an infinite query using queryClient.setQueryData. ```tsx queryClient.setQueryData(['projects'], (data) => ({ pages: data.pages.slice(1), pageParams: data.pageParams.slice(1), })) ```

Manually remove single value from infinite query page example

Example: Removing a single item from an individual page in an infinite query using queryClient.setQueryData and filter. ```tsx const newPagesArray = oldPagesArray?.pages.map((page) => page.filter((val) => val.id !== updatedId), ) ?? [] queryClient.setQueryData(['projects'], (data) => ({ pages: newPagesArray, pageParams: data.pageParams, })) ```

Keep only first page example

Example: Keeping only the first page in an infinite query using queryClient.setQueryData. ```tsx queryClient.setQueryData(['projects'], (data) => ({ pages: data.pages.slice(0, 1), pageParams: data.pageParams.slice(0, 1), })) ```

Limited infinite query with maxPages example

Example: Implementing a limited infinite query that keeps only 3 pages in the query data using the maxPages option. ```tsx useInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor, maxPages: 3, }) ```

Infinite query without API cursor using pageParam example

Example: Implementing infinite query pagination when API doesn't return a cursor, using pageParam to calculate next and previous page params. ```tsx return useInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) => { if (lastPage.length === 0) { return undefined } return lastPageParam + 1 }, getPreviousPageParam: (firstPage, allPages, firstPageParam) => { if (firstPageParam <= 1) { return undefined } return firstPageParam - 1 }, }) ```

Infinite query page params via QueryFunctionContext.pageParam

In React Query v3, infinite query page params are now passed via QueryFunctionContext.pageParam instead of as the last query key parameter. Example: useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPosts(pageParam))

useInfiniteQuery is now bi-directional in v3

In React Query v3, useInfiniteQuery() now supports bi-directional infinite lists with the following changes: options.getFetchMore renamed to options.getNextPageParam; queryResult.canFetchMore renamed to queryResult.hasNextPage; queryResult.fetchMore renamed to queryResult.fetchNextPage; queryResult.isFetchingMore renamed to queryResult.isFetchingNextPage; added options.getPreviousPageParam, queryResult.hasPreviousPage, queryResult.fetchPreviousPage, and queryResult.isFetchingPreviousPage. The data is now an object containing pages and pageParams: { pages: [data, data, data], pageParams: [...] }

Infinite query data structure with pages and pageParams

In React Query v3, infinite query data now contains an array of pages and pageParams used to fetch those pages. This allows easier manipulation like removing the first page: queryClient.setQueryData(['projects'], (data) => ({ pages: data.pages.slice(1), pageParams: data.pageParams.slice(1), }))

InfiniteQueryObserver for watching infinite queries

In React Query v3, InfiniteQueryObserver can be used to create and watch an infinite query. Example: const observer = new InfiniteQueryObserver(queryClient, { queryKey: 'posts', queryFn: fetchPosts, getNextPageParam: (lastPage, allPages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor }); const unsubscribe = observer.subscribe((result) => { console.log(result) })

v5 maxPages option for limiting infinite queries

In v5, infinite queries have a new maxPages option to limit the number of pages stored in query data and refetched. This reduces memory consumption and improves refetch performance. The infinite list must be bi-directional, requiring both getNextPageParam and getPreviousPageParam to be defined.

v5 infinite queries prefetch multiple pages

In v5, infinite queries can be prefetched like regular queries. By default, only the first page is prefetched. To prefetch multiple pages, use the pages option in the prefetch call.

placeholderData with infinite queries

The placeholderData option also works with useInfiniteQuery, allowing users to continue seeing cached data while infinite query keys change over time.

prefetchInfiniteQuery with multiple pages

By default, prefetchInfiniteQuery prefetches only the first page of a query. To prefetch more than one page, use the pages option and also provide a getNextPageParam function.

prefetchInfiniteQuery example with pages option

Example showing how to prefetch multiple pages with prefetchInfiniteQuery: ```tsx const prefetchProjects = async () => { await queryClient.prefetchInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.nextCursor, pages: 3, // prefetch the first 3 pages }) } ```

infiniteQueryOptions helper for infinite queries

A separate infiniteQueryOptions helper is available for use with Infinite Queries, similar to queryOptions but designed for infinite query configuration.

infiniteQueryOptions function and parameters

infiniteQueryOptions is a function that generates options for infinite queries. It accepts an object with a required queryKey parameter and spread options. The queryKey is a QueryKey that is required. You can pass most options that you would pass to useInfiniteQuery, though some options may have no effect when forwarded to functions like queryClient.prefetchInfiniteQuery. TypeScript will not error on excess properties.

useInfiniteQuery page fetching flags

useInfiniteQuery returns isFetchingNextPage: boolean—true while fetching the next page with fetchNextPage; isFetchingPreviousPage: boolean—true while fetching the previous page with fetchPreviousPage; isFetchNextPageError: boolean—true if the query failed while fetching the next page; isFetchPreviousPageError: boolean—true if the query failed while fetching the previous page.

useInfiniteQuery hook signature and destructuring

The useInfiniteQuery hook returns an object that can be destructured to get: fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage, isFetchingNextPage, isFetchingPreviousPage, promise, and all properties from the base useQuery hook result.

useInfiniteQuery options reference

useInfiniteQuery accepts all options from useQuery with the following required additions: queryFn (context: QueryFunctionContext) => Promise<TData>—required unless a default query function is defined, receives a QueryFunctionContext and must return a promise; initialPageParam: TPageParam—required, the default page param for the first page; getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => TPageParam | undefined | null—required, receives the last page and full pages array plus pageParam info, must return a single variable to pass to queryFn or undefined/null for no next page; getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) => TPageParam | undefined | null—optional, same behavior as getNextPageParam but for previous pages; maxPages: number | undefined—optional, maximum number of pages to store, when reached fetching a new page removes the first or last page, undefined or 0 means unlimited, default is undefined, and both getNextPageParam and getPreviousPageParam must be defined if maxPages is greater than 0.

useInfiniteQuery data structure

The data returned from useInfiniteQuery contains: data.pages—an array containing all pages; data.pageParams—an array containing all page params.

fetchNextPage and fetchPreviousPage functions

fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult> allows fetching the next page of results. fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult> allows fetching the previous page of results. Both accept an options.cancelRefetch parameter: if true, calling the function repeatedly will invoke queryFn every time regardless of previous invocations, and results from previous invocations are ignored; if false, calling repeatedly won't have effect until the first invocation resolves. Default for cancelRefetch is true.

hasNextPage and hasPreviousPage flags

hasNextPage: boolean—true if there is a next page to be fetched (determined via the getNextPageParam option). hasPreviousPage: boolean—true if there is a previous page to be fetched (determined via the getPreviousPageParam option).

useInfiniteQuery isRefetching behavior differs from useQuery

In useInfiniteQuery, isRefetching: boolean is true whenever a background refetch is in-flight, which does not include initial pending state or fetching of next or previous page. It is equivalent to: isFetching && !isPending && !isFetchingNextPage && !isFetchingPreviousPage.

useInfiniteQuery isRefetchError

isRefetchError: boolean—true if the query failed while refetching a page.

useInfiniteQuery promise property

The promise property returns a stable promise that resolves to the query result. This can be used with React.use() to fetch data and requires the experimental_prefetchInRender feature flag to be enabled on the QueryClient.

Pitfall: fetchNextPage may interfere with default refetch behavior

Imperative fetch calls such as fetchNextPage may interfere with the default refetch behaviour and result in outdated data. These functions should only be called in response to user actions, or conditions should be added like hasNextPage && !isFetching to prevent issues.

useInfiniteQuery example with getNextPageParam and getPreviousPageParam

Example showing useInfiniteQuery setup: const { fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage, isFetchingNextPage, isFetchingPreviousPage, promise, ...result } = useInfiniteQuery({ queryKey, queryFn: ({ pageParam }) => fetchPage(pageParam), initialPageParam: 1, ...options, getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPage.nextCursor, getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) => firstPage.prevCursor, })

usePrefetchInfiniteQuery hook overview

usePrefetchInfiniteQuery is a hook that accepts options to prefetch an infinite query during render. It does not return anything and should be used to fire a prefetch before a suspense boundary that wraps a component using useSuspenseInfiniteQuery.

usePrefetchInfiniteQuery required options

usePrefetchInfiniteQuery requires the following options: queryKey (QueryKey, required), queryFn (a function returning Promise<TData>, required only if no default query function is defined), initialPageParam (TPageParam, required - the default page param for fetching the first page), and getNextPageParam (a function that receives lastPage, allPages, lastPageParam, allPageParams and returns TPageParam | undefined | null, required).

getNextPageParam function behavior

The getNextPageParam function receives the last page of the infinite list, the full array of all pages, and pageParam information. It should return a single variable to pass as the last parameter to the query function, or return undefined or null to indicate there is no next page available.

usePrefetchInfiniteQuery accepts queryClient.prefetchInfiniteQuery options

usePrefetchInfiniteQuery accepts all options that can be passed to queryClient.prefetchInfiniteQuery, with some being required as documented.

Prevent duplicate fetchNextPage calls

To prevent triggering multiple fetch requests when a user clicks load more before the previous fetch completes, check query.isFetching() before calling query.fetchNextPage(). Return early if isFetching() is true.

injectInfiniteQuery basic setup with initialPageParam

To set up an infinite query in Angular, use injectInfiniteQuery with required properties including queryKey, queryFn, and initialPageParam. The initialPageParam sets the initial value passed to queryFn via pageParam. The queryFn receives an object with pageParam property. Use getNextPageParam and getPreviousPageParam to determine how to fetch subsequent pages by examining the data returned from previous pages.

injectInfiniteQuery full parameter reference

injectInfiniteQuery accepts a configuration object with: queryKey (array, required), queryFn (function receiving { pageParam }, required), initialPageParam (the initial page param value, required), getNextPageParam (function(lastPage, pages) returning next pageParam or undefined), getPreviousPageParam (function(firstPage, pages) returning previous pageParam or undefined), maxPages (number, optional, limits number of pages kept in memory), and select (optional function to transform the data).

getNextPageParam and getPreviousPageParam function signatures

getNextPageParam receives (lastPage, pages) where lastPage is the most recent page data and pages is an array of all fetched pages. It should return the next pageParam value or undefined if there are no more pages. getPreviousPageParam receives (firstPage, pages) and returns the previous pageParam value or undefined. Alternative signature with three parameters: (lastPage, allPages, lastPageParam) or (firstPage, allPages, firstPageParam) provides access to the current pageParam value being used.

Accessing infinite query result data and pagination state

The result from injectInfiniteQuery provides: query.data() which returns { pages, pageParams } where pages is an array of page results; query.isPending() for loading state; query.isError() for error state; query.error() for error details; query.hasNextPage for whether more pages are available; query.isFetchingNextPage() for checking if next page is currently loading; query.isFetching() for any fetch in progress; and query.fetchNextPage() to load the next page.

Rendering infinite query data in Angular template

Infinite query results are rendered by iterating over query.data().pages using @for loops. Each page contains the data returned from queryFn. Example: @for (page of query.data().pages; track $index) { @for (project of page.data; track project.id) { display project } }.

maxPages parameter limits cached pages

The maxPages option in injectInfiniteQuery configuration limits the number of pages kept in the infinite query's data. For example, maxPages: 3 will keep only the most recent 3 pages in memory, removing older pages automatically.

Reverse page order using select in infinite queries

To display pages in reverse order, use the select option in injectInfiniteQuery configuration: select: (data) => ({ pages: [...data.pages].reverse(), pageParams: [...data.pageParams].reverse() }). This reverses both the pages array and pageParams array.

Infinite query with numeric offset pagination

For offset-based pagination, getNextPageParam can calculate the next page number from the lastPageParam: getNextPageParam: (lastPage, allPages, lastPageParam) => { if (lastPage.length === 0) return undefined; return lastPageParam + 1 }. getPreviousPageParam similarly returns previousPageParam - 1 when not at first page: getPreviousPageParam: (firstPage, allPages, firstPageParam) => { if (firstPageParam <= 1) return undefined; return firstPageParam - 1 }.

Cursor-based pagination with getNextPageParam

For cursor-based pagination, extract the cursor from the API response and return it from getNextPageParam and getPreviousPageParam. Example: getNextPageParam: (lastPage, pages) => lastPage.nextCursor. The cursor value becomes the pageParam passed to the next queryFn call.

Infinite query Load More example with Lit

Example using createInfiniteQueryController in Lit: ```ts import { LitElement, html } from 'lit' import { createInfiniteQueryController } from '@tanstack/lit-query' class ProjectsList extends LitElement { private readonly projects = createInfiniteQueryController(this, { queryKey: ['projects'], queryFn: ({ pageParam }) => fetchProjectsPage(pageParam), initialPageParam: 1, getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.page + 1 : undefined, }) render() { const query = this.projects() if (query.isPending) return html`Loading...` if (query.isError) return html`Error: ${query.error.message}` return html` ${query.data.pages.map( (page) => html` ${page.projects.map((project) => html`<p>${project.name}</p>`)} `, )} <button ?disabled=${!query.hasNextPage || query.isFetching} @click=${() => this.projects.fetchNextPage()} > ${query.isFetchingNextPage ? 'Loading more...' : query.hasNextPage ? 'Load More' : 'Nothing more to load'} </button> ` } } ``` This example shows how to render pages of data, display loading states, and provide a button to load more pages.

Give your agent this brain