useInfiniteQuery composable. It manages multiple pages of data and provides methods to load more pages.
Signature
function useInfiniteQuery<TQueryFnData, TError, TData, TQueryKey, TPageParam>(
options: UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,
queryClient?: QueryClient,
): UseInfiniteQueryReturnType<TData, TError>
Parameters
Configuration options for the infinite query. Can be a reactive ref or getter function.
Show properties
Show properties
Unique identifier for the query. Can be a ref or contain refs.
queryFn
MaybeRefDeep<(context: QueryFunctionContext<TQueryKey, TPageParam>) => Promise<TQueryFnData>>
required
Function that fetches a page of data. Receives
pageParam in context. Can be a ref.The initial page parameter for the first page.
getNextPageParam
MaybeRefDeep<(lastPage: TQueryFnData, allPages: TQueryFnData[], lastPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null>
required
Function to determine the next page parameter. Return
undefined or null when there are no more pages. Can be a ref.getPreviousPageParam
MaybeRefDeep<(firstPage: TQueryFnData, allPages: TQueryFnData[], firstPageParam: TPageParam, allPageParams: TPageParam[]) => TPageParam | undefined | null>
Function to determine the previous page parameter for bi-directional pagination. Can be a ref.
Set to
false to disable automatic query execution. Supports refs and getters.Time in milliseconds until cached data is considered stale. Can be a ref.
Time in milliseconds before unused data is garbage collected. Can be a ref.
Refetch when window regains focus. Can be a ref.
Transform or select a part of the data. Can be a ref.
Return data in a shallow ref (improves performance if data doesn’t need deep reactivity).
Custom QueryClient instance. If not provided, uses the client from context.
Returns
Reactive refs containing infinite query state and methods.
Show properties
Show properties
Object containing all pages of data with
pages and pageParams arrays.The error object if the query failed.
true when fetching the first page for the first time.true whenever the query is fetching (including additional pages).true when fetching the next page.true when fetching the previous page.true if there is a next page to fetch.true if there is a previous page to fetch.Fetch the next page of data.
Fetch the previous page of data.
true when the query has successfully fetched data.true when the query encountered an error.The current status of the query.
Manually trigger a refetch of all pages.
Type Parameters
TQueryFnData- Type of data returned by each pageTError- Type of error (defaults toDefaultError)TData- Type of final data (defaults toInfiniteData<TQueryFnData>)TQueryKey- Type of the query key (defaults toQueryKey)TPageParam- Type of page parameter (defaults tounknown)
Examples
Basic Infinite Scroll
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`/api/posts?page=${pageParam}`)
return res.json()
},
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
return lastPage.hasMore ? allPages.length : undefined
},
})
</script>
<template>
<div>
<div v-for="page in data?.pages" :key="page.id">
<div v-for="post in page.posts" :key="post.id">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</div>
</div>
<button
@click="fetchNextPage()"
:disabled="!hasNextPage || isFetchingNextPage"
>
<span v-if="isFetchingNextPage">Loading more...</span>
<span v-else-if="hasNextPage">Load More</span>
<span v-else>No more posts</span>
</button>
</div>
</template>
Cursor-Based Pagination
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`/api/posts?cursor=${pageParam}`)
return res.json()
},
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
</script>
Bi-directional Pagination
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'
const {
data,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`/api/posts?cursor=${pageParam}`)
return res.json()
},
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
})
</script>
<template>
<div>
<button @click="fetchPreviousPage()" :disabled="!hasPreviousPage">
Load Previous
</button>
<!-- Posts display -->
<button @click="fetchNextPage()" :disabled="!hasNextPage">
Load Next
</button>
</div>
</template>
With TypeScript
<script setup lang="ts">
import { useInfiniteQuery } from '@tanstack/vue-query'
interface Post {
id: number
title: string
body: string
}
interface PostsPage {
posts: Post[]
nextCursor?: number
hasMore: boolean
}
const { data } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam }): Promise<PostsPage> => {
const res = await fetch(`/api/posts?cursor=${pageParam}`)
return res.json()
},
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
// data.pages is typed as PostsPage[]
</script>
Reactive Page Parameters
<script setup>
import { ref } from 'vue'
import { useInfiniteQuery } from '@tanstack/vue-query'
const filter = ref('all')
const { data, fetchNextPage } = useInfiniteQuery({
queryKey: ['posts', filter], // Refetches when filter changes
queryFn: async ({ pageParam }) => {
const res = await fetch(
`/api/posts?filter=${filter.value}&cursor=${pageParam}`
)
return res.json()
},
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
</script>
<template>
<select v-model="filter">
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
</template>
Infinite Scroll with Intersection Observer
<script setup>
import { ref, watch } from 'vue'
import { useInfiniteQuery } from '@tanstack/vue-query'
const loadMoreRef = ref(null)
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
// Auto-load more when element is visible
watch(loadMoreRef, (el) => {
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && hasNextPage.value && !isFetchingNextPage.value) {
fetchNextPage()
}
},
{ threshold: 1.0 }
)
observer.observe(el)
})
</script>
<template>
<div>
<div v-for="page in data?.pages" :key="page.id">
<div v-for="post in page.posts" :key="post.id">
{{ post.title }}
</div>
</div>
<div ref="loadMoreRef" v-if="hasNextPage">
<span v-if="isFetchingNextPage">Loading...</span>
</div>
</div>
</template>
Refetch All Pages
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'
const { data, refetch } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
const refreshAll = () => {
// Refetches all loaded pages
refetch()
}
</script>
Related
- useQuery - For non-paginated data
- Infinite Queries Guide - Detailed guide on infinite queries