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
})






