Skip to main content

Command Palette

Search for a command to run...

Infinite Queries in React Query

Updated
2 min read
Infinite Queries in React Query

Core Pattern: useInfiniteQuery

Instead of managing page state yourself, React Query handles it:

function usePosts() {
  return useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam }) => fetchPosts(pageParam), // pageParam managed by RQ
    initialPageParam: 1, // Where to start
    getNextPageParam: (lastPage, allPages, lastPageParam) => {
      if (lastPage.length === 0) {
        return undefined // No more pages
      }
      return lastPageParam + 1
    }
  })
}

Data Structure: Pages Array

You get back a different data shape:

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts()

// data.pages = [
//   [post1, post2, post3],  // page 1
//   [post4, post5, post6],  // page 2  
//   [post7, post8, post9]   // page 3
// ]

const allPosts = data?.pages.flat() // Flatten into single array

Infinite Scroll Implementation

Trigger next page on scroll:

import { useIntersectionObserver } from "@uidotdev/usehooks"

function InfinitePostList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts()
  const [ref, entry] = useIntersectionObserver()

  // Auto-fetch when scrolled to bottom
  React.useEffect(() => {
    if (entry?.isIntersecting && hasNextPage && !isFetchingNextPage) {
      fetchNextPage()
    }
  }, [entry?.isIntersecting, hasNextPage, isFetchingNextPage, fetchNextPage])

  return (
    <div>
      {data?.pages.flat().map(post => (
        <PostCard key={post.id} post={post} />
      ))}

      {/* Trigger element at bottom */}
      <div ref={ref}>
        {isFetchingNextPage ? 'Loading more...' : 'Scroll for more'}
      </div>
    </div>
  )
}

Cursor-Based APIs

For APIs that return cursors instead of page numbers:

function useProjects() {
  return useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: ({ pageParam = 0 }) => fetchProjects(pageParam),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => {
      return lastPage.nextCursor ?? undefined // Return cursor or undefined
    }
  })
}

Bidirectional Infinite Scroll

For chat apps where you can scroll up and down:

useInfiniteQuery({
  queryKey: ['messages', chatId],
  queryFn: ({ pageParam }) => fetchMessages(chatId, pageParam),
  initialPageParam: 50, // Start in middle
  getNextPageParam: (lastPage, allPages, lastPageParam) => {
    return lastPage.length ? lastPageParam + 1 : undefined
  },
  getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
    return firstPageParam > 1 ? firstPageParam - 1 : undefined
  }
})

Memory Management

Prevent infinite memory growth if necessary:

useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam }) => fetchPosts(pageParam),
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages, lastPageParam) => {
    return lastPage.length ? lastPageParam + 1 : undefined
  },
  maxPages: 3 // Only keep 3 pages in cache
})