Skip to main content
The useInfiniteQuery hook is used for fetching paginated or infinite scrolling data. It manages multiple pages of data and provides helpers for loading more pages.

Import

import { useInfiniteQuery } from '@tanstack/react-query'

Signature

function useInfiniteQuery<
  TQueryFnData,
  TError = DefaultError,
  TData = InfiniteData<TQueryFnData>,
  TQueryKey extends QueryKey = QueryKey,
  TPageParam = unknown,
>(
  options: UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,
  queryClient?: QueryClient,
): UseInfiniteQueryResult<TData, TError>

Parameters

options
object
required
The infinite query options object.
queryClient
QueryClient
Override the default QueryClient.

Returns

UseInfiniteQueryResult<TData, TError>
object
The infinite query result object.

Examples

Cursor-based Pagination

import { useInfiniteQuery } from '@tanstack/react-query'

interface Project {
  id: number
  name: string
}

interface ProjectsResponse {
  data: Project[]
  nextCursor?: number
}

function Projects() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: async ({ pageParam }) => {
      const response = await fetch(`/api/projects?cursor=${pageParam}`)
      return response.json() as Promise<ProjectsResponse>
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  })

  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.data.map(project => (
            <div key={project.id}>{project.name}</div>
          ))}
        </div>
      ))}
      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage
          ? 'Loading more...'
          : hasNextPage
            ? 'Load More'
            : 'Nothing more to load'}
      </button>
    </div>
  )
}

Offset/Limit Pagination

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: async ({ pageParam }) => {
    const response = await fetch(
      `/api/projects?limit=10&offset=${pageParam}`
    )
    return response.json()
  },
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages, lastPageParam) => {
    if (lastPage.length === 0) return undefined
    return lastPageParam + 10
  },
})

Bi-directional Pagination

const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: async ({ pageParam }) => fetchProjects(pageParam),
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage) => firstPage.prevCursor,
})

With Max Pages

const { data } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
  maxPages: 3, // Only keep 3 pages in memory
})

Flattening Pages with Select

const { data } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
  select: data => ({
    pages: data.pages.flatMap(page => page.data),
    pageParams: data.pageParams,
  }),
})

// data.pages is now a flat array of all items

Notes

The initialPageParam is required and should match the type of your page parameters.
Return undefined or null from getNextPageParam to indicate there are no more pages.
The data object has a specific shape: { pages: TQueryFnData[], pageParams: TPageParam[] }

Build docs developers (and LLMs) love