Skip to main content
Juliano Alves
Back to blog

Infinite queries with TanStack Query

3 min read
By Juliano Alves

Paginated feeds—social timelines, admin tables, search results—map naturally to useInfiniteQuery: each “page” is a fetch keyed by a cursor or offset, and TanStack Query merges results while preserving per-page cache entries.

Minimal shape#

import { useInfiniteQuery } from '@tanstack/react-query';

type Page = { items: { id: string }[]; nextCursor: string | null };

export function useFeed() {
  return useInfiniteQuery({
    queryKey: ['feed'],
    initialPageParam: null as string | null,
    queryFn: async ({ pageParam }) => {
      const url = new URL('/api/feed', window.location.origin);
      if (pageParam) url.searchParams.set('cursor', pageParam);
      const res = await fetch(url);
      if (!res.ok) throw new Error('feed failed');
      return res.json() as Promise<Page>;
    },
    getNextPageParam: (last) => last.nextCursor,
  });
}

Render with data.pages.flatMap((p) => p.items) or keep nested structure if you need per-page metadata.

Stable queryKey#

Include every variable that changes the result set: ['feed', { filter, sort }] not only ['feed']. Otherwise stale pages bleed across user actions.

Mutations and list invalidation#

After creating a post, either:

  • queryClient.invalidateQueries({ queryKey: ['feed'] }) (simple, refetches from scratch), or
  • setQueryData to prepend optimistically (faster UX, more code).

For infinite lists, setQueryData must respect the pages array shape—utility helpers in the docs flatten this operation.

Bi-directional infinite scroll#

getPreviousPageParam enables “load older” above the fold. Combine with virtualized lists (@tanstack/react-virtual) so DOM nodes stay bounded.

Errors and retries#

Use maxPages (v5) or manual guards to cap memory if the user scrolls forever. Surface fetchNextPage errors inline; do not swallow partial failures silently.

Summary#

useInfiniteQuery is the right primitive when the server speaks cursors or numbered pages and the UI stacks them. Invest in queryKey hygiene and a clear invalidation story when mutations touch the same list.

© 2026 Juliano Alves. All rights reserved.