Skip to main content

useInfiniteQuery

The useInfiniteQuery hook is used for fetching data that is paginated or loaded incrementally (infinite scroll). It extends useQuery with additional functionality for managing multiple pages of data.

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>

Type Parameters

TQueryFnData
type
The type of data returned by the query function for a single page
TError
type
default:"DefaultError"
The type of error that can be thrown by the query function
TData
type
default:"InfiniteData<TQueryFnData>"
The type of data returned by the select function (if provided)
TQueryKey
type
default:"QueryKey"
The type of the query key
TPageParam
type
default:"unknown"
The type of the page parameter used for pagination

Parameters

options
UseInfiniteQueryOptions
required
Configuration options for the infinite query. Extends UseQueryOptions with additional infinite-specific options.
queryKey
TQueryKey
required
A unique key for the query. Must be an array.
queryFn
(context: QueryFunctionContext<TQueryKey, TPageParam>) => Promise<TQueryFnData>
required
The function that fetches a single page of data. Receives pageParam in the context.
initialPageParam
TPageParam
required
The default page parameter to use for the initial page
getNextPageParam
(lastPage: TQueryFnData, allPages: TQueryFnData[], lastPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null
required
Function that returns the next page parameter. Return undefined or null to indicate there are no more pages.
getPreviousPageParam
(firstPage: TQueryFnData, allPages: TQueryFnData[], firstPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null
Function that returns the previous page parameter for bi-directional infinite queries
maxPages
number
Maximum number of pages to store in the query data at once. When the maximum is reached, fetching a new page will remove either the first or last page from the data, depending on the direction of the fetch.
enabled
boolean
default:"true"
Set to false to disable automatic query execution
staleTime
number | ((query: Query) => number)
default:"0"
Time in milliseconds after data is considered stale
gcTime
number
default:"300000"
Time in milliseconds that unused/inactive cache data remains in memory
refetchOnWindowFocus
boolean | 'always'
default:"true"
If set to true, the query will refetch on window focus if the data is stale
refetchOnMount
boolean | 'always'
default:"true"
If set to true, the query will refetch on mount if the data is stale
refetchOnReconnect
boolean | 'always'
default:"true"
If set to true, the query will refetch on reconnect if the data is stale
retry
boolean | number | (failureCount: number, error: TError) => boolean
default:"3"
Number of retry attempts or function to determine if a request should be retried
select
(data: InfiniteData<TQueryFnData>) => TData
Function to transform or select a part of the data returned by the query function
subscribed
boolean
default:"true"
Set this to false to unsubscribe this observer from updates to the query cache
queryClient
QueryClient
Optional QueryClient instance to use. If not provided, the client from the nearest QueryClientProvider will be used.

Returns

Returns all properties from useQuery plus the following:
data
InfiniteData<TQueryFnData>
The aggregated data from all pages:
{
  pages: TQueryFnData[],
  pageParams: TPageParam[]
}
fetchNextPage
(options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>
Function to fetch the next page of data. Options:
  • cancelRefetch?: boolean - Cancel any ongoing refetch
fetchPreviousPage
(options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>
Function to fetch the previous page of data
hasNextPage
boolean
Will be true if there is a next page to fetch (i.e., getNextPageParam returned a value other than undefined or null)
hasPreviousPage
boolean
Will be true if there is a previous page to fetch
isFetchingNextPage
boolean
Will be true while fetching the next page
isFetchingPreviousPage
boolean
Will be true while fetching the previous page
isFetching
boolean
Will be true whenever a fetch is in progress (including background refetches and next/previous page fetches)

Examples

Basic Usage

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

interface TodosResponse {
  data: Todo[]
  nextCursor: number | null
}

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

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

With Type Safety

interface Todo {
  id: number
  title: string
}

interface TodosResponse {
  items: Todo[]
  nextPage: number | undefined
}

function TodoList() {
  const query = useInfiniteQuery<
    TodosResponse,  // TQueryFnData
    Error,          // TError
    InfiniteData<TodosResponse>, // TData
    string[],       // TQueryKey
    number          // TPageParam
  >({
    queryKey: ['todos'],
    queryFn: async ({ pageParam }) => {
      const response = await fetch(`/api/todos?page=${pageParam}`)
      return response.json()
    },
    initialPageParam: 1,
    getNextPageParam: (lastPage) => lastPage.nextPage,
  })
}

Bi-directional Infinite Query

function Feed() {
  const {
    data,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
  } = useInfiniteQuery({
    queryKey: ['feed'],
    queryFn: async ({ pageParam }) => {
      const response = await fetch(`/api/feed?cursor=${pageParam}`)
      return response.json()
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  })

  return (
    <>
      <button
        onClick={() => fetchPreviousPage()}
        disabled={!hasPreviousPage}
      >
        Load Newer
      </button>

      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.items.map((item) => (
            <div key={item.id}>{item.content}</div>
          ))}
        </div>
      ))}

      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage}
      >
        Load Older
      </button>
    </>
  )
}

Infinite Scroll with Intersection Observer

import { useInfiniteQuery } from '@tanstack/react-query'
import { useInView } from 'react-intersection-observer'
import { useEffect } from 'react'

function InfiniteScrollList() {
  const { ref, inView } = useInView()

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['items'],
    queryFn: fetchItems,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  })

  useEffect(() => {
    if (inView && hasNextPage) {
      fetchNextPage()
    }
  }, [inView, hasNextPage, fetchNextPage])

  return (
    <>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.items.map((item) => (
            <div key={item.id}>{item.title}</div>
          ))}
        </div>
      ))}
      <div ref={ref}>
        {isFetchingNextPage && <div>Loading more...</div>}
      </div>
    </>
  )
}

With Data Transformation

function TodoList() {
  const { data } = useInfiniteQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    select: (data) => ({
      pages: data.pages.map((page) => 
        page.items.filter((item) => !item.completed)
      ),
      pageParams: data.pageParams,
    }),
  })

  // Only incomplete todos are returned
}

Source

Implementation: useInfiniteQuery.ts:72

Build docs developers (and LLMs) love