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
object
required
The infinite query options object.
Show properties
Show properties
QueryKey
required
A unique key for the query. Must be an array.
queryKey: ['projects']
queryKey: ['projects', { status: 'active' }]
QueryFunction<TQueryFnData, TQueryKey, TPageParam>
required
The function that fetches data for each page. Receives a
QueryFunctionContext with:queryKey: TQueryKey- The query keypageParam: TPageParam- The current page parametersignal: AbortSignal- AbortSignal for cancellationmeta: QueryMeta | undefined- Optional metadatadirection: FetchDirection- Direction of pagination ('forward'or'backward')
queryFn: async ({ pageParam }) => {
const response = await fetch(`/api/projects?cursor=${pageParam}`)
return response.json()
}
TPageParam
required
The default page parameter to use when fetching the first page.
initialPageParam: 0
initialPageParam: undefined
initialPageParam: null
(lastPage: TQueryFnData, allPages: TQueryFnData[], lastPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null
required
Function to determine the next page parameter. Return
undefined or null to indicate no more pages.getNextPageParam: (lastPage, allPages, lastPageParam) => lastPage.nextCursor
getNextPageParam: (lastPage) => lastPage.nextId ?? undefined
(firstPage: TQueryFnData, allPages: TQueryFnData[], firstPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null
Function to determine the previous page parameter. Return
undefined or null to indicate no more pages.getPreviousPageParam: (firstPage) => firstPage.prevCursor ?? undefined
number
Maximum number of pages to store in the data. When the maximum is reached, fetching a new page will remove the furthest page from the opposite direction.
maxPages: 3
boolean | ((query: Query) => boolean)
default:"true"
Set to
false to disable automatic execution.number | 'static' | ((query: Query) => number | 'static')
default:"0"
The time in milliseconds after data is considered stale.
number
default:"300000"
The time in milliseconds that unused/inactive cache data remains in memory.
number | false | ((query: Query) => number | false)
default:"false"
If set to a number, the query will continuously refetch at this frequency.
boolean
default:"false"
If
true, refetch will continue while the tab is in the background.boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
Refetch on window focus behavior.
boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
Refetch on reconnect behavior.
boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
Refetch on mount behavior.
boolean | number | ((failureCount: number, error: TError) => boolean)
default:"3"
Retry configuration for failed queries.
number | ((failureCount: number, error: TError) => number)
Delay between retries in milliseconds.
(data: InfiniteData<TQueryFnData, TPageParam>) => TData
Transform or select part of the infinite data.
select: data => ({
pages: data.pages.map(page => page.items).flat(),
pageParams: data.pageParams,
})
boolean | ((error: TError, query: Query) => boolean)
default:"false"
Set to
true to throw errors to an error boundary.'online' | 'always' | 'offlineFirst'
default:"'online'"
Network mode configuration.
Record<string, unknown>
Optional metadata.
QueryClient
Override the default QueryClient.
Returns
object
The infinite query result object.
Show properties
Show properties
InfiniteData<TQueryFnData, TPageParam> | undefined
The aggregated data object containing all pages and page parameters.
{
pages: TQueryFnData[],
pageParams: TPageParam[]
}
TError | null
The error object for the query, if an error was thrown.
'pending' | 'error' | 'success'
The status of the query.
'fetching' | 'paused' | 'idle'
The fetch status of the query.
boolean
true if the query is in pending state.boolean
true if the first fetch is in progress.boolean
true if the queryFn is executing.boolean
true if the query has succeeded.boolean
true if the query has errored.boolean
true if there is a next page to fetch (determined by getNextPageParam).boolean
true if there is a previous page to fetch (determined by getPreviousPageParam).boolean
true while fetching the next page with fetchNextPage.boolean
true while fetching the previous page with fetchPreviousPage.boolean
true if the query failed while fetching the next page.boolean
true if the query failed while fetching the previous page.(options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>
Function to fetch the next page of results.
fetchNextPage({ cancelRefetch: true })
(options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>
Function to fetch the previous page of results.
fetchPreviousPage({ cancelRefetch: true })
(options?: RefetchOptions) => Promise<UseInfiniteQueryResult>
Function to manually refetch all pages.
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[] }