---
id: query.infinite-cursor
kind: recipe
product: query
framework: react
locale: en
revision: 1
sourceCheckedOn: "2026-08-03"
versionRange: "^5"
verifiedAgainst: "@tanstack/react-query@5.101.4"
contentModel: 2
packages:
  - "@tanstack/react-query"
tasks:
  - "build-infinite-query"
  - "paginate-by-cursor"
  - "bound-query-pages"
sourceRefs:
  - "https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries"
  - "https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery"
---

# Build a bounded cursor Infinite Query

> Use one queryKey for the full list, let server cursors drive pageParam, and bound cache and refetch cost with maxPages.

## Execution contract

- **Use when:** Use for a list that appends pages and receives the next cursor or explicit offset from the server contract.
- **Avoid when:** Prefer URL page state and a regular Query when pagination needs shareable page numbers or random access.
- **Preconditions:** Define initialPageParam, final-page signal, stable query key, page response shape, and whether maxPages or bidirectional paging is required.
- **Verification:** Record sequential pageParams and test final-page, retry, filter changes, and repeated clicks; pages and pageParams must always correspond.
- **Failure mode:** Duplicate pages usually come from guessed cursors, a stale getNextPageParam result, or concurrent fetchNextPage calls; inspect network records and the last-page response.
- **Security:** Cursors are tamperable external input; the server must validate scope, ordering, and user permissions rather than treating a cursor as authorization.

## Input contract

Define the first pageParam, response nextCursor, final-page signal, and list filters. Filters belong in queryKey and cursors belong in pageParam. For offset APIs, derive the next value explicitly from lastPageParam rather than component-local page state.

## Minimal implementation

initialPageParam and getNextPageParam are required v5 contracts. Return undefined to end the list and propagate AbortSignal to stop obsolete work. When maxPages is positive, bidirectional pagination also requires getPreviousPageParam.

File: `src/features/issues/queries.ts`

```ts
import { infiniteQueryOptions } from '@tanstack/react-query'

export const issueFeedOptions = infiniteQueryOptions({
  queryKey: ['issues', 'feed'] as const,
  initialPageParam: null as string | null,
  queryFn: ({ pageParam, signal }) => fetchIssuePage(pageParam, signal),
  getNextPageParam: (page) => page.nextCursor ?? undefined,
  maxPages: 5,
})
```

## Cache and interaction checks

Flatten data.pages for rendering but preserve `{ pages, pageParams }` in cache writes. Gate load-more with both hasNextPage and isFetchingNextPage; test final-page, retry, filter changes, rapid repeated triggers, and maxPages eviction direction.

## Official sources

- https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries
- https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery
