Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TanStack/query/llms.txt

Use this file to discover all available pages before exploring further.

Fetch paginated or infinite scrolling data with the 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

options
UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>
required
Configuration options for the infinite query. Can be a reactive ref or getter function.
queryClient
QueryClient
Custom QueryClient instance. If not provided, uses the client from context.

Returns

UseInfiniteQueryReturnType<TData, TError>
object
Reactive refs containing infinite query state and methods.

Type Parameters

  • TQueryFnData - Type of data returned by each page
  • TError - Type of error (defaults to DefaultError)
  • TData - Type of final data (defaults to InfiniteData<TQueryFnData>)
  • TQueryKey - Type of the query key (defaults to QueryKey)
  • TPageParam - Type of page parameter (defaults to unknown)

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>

Build docs developers (and LLMs) love