gridcn1.2.2

TanStack Query

Lazy ranges through queryClient.fetchQuery, which dedupes requests and caches each range for the session, so scrolling back never refetches.

Each scrolled range becomes a fetchQuery: TanStack Query dedupes concurrent ranges, keeps every fetched range in the cache (staleTime: Infinity), and the hook's abort signal still cancels the network call when a range leaves view. Scrolling back never refetches. The "ranges in the query cache" counter in the demo bar makes that visible.

0 / 10,000 rows loaded0 windows in the query cache
ID
Name
Email
Score
pnpm dlx shadcn@latest add @gridcn/data-grid-lazy-react-query-demo

The seam

const queryClient = useQueryClient();

const lazy = useDataGridLazyRows<Row>({
  total: 10_000,
  getRowId: (row) => row.id,
  fetchRows: (start, end, signal) =>
    queryClient.fetchQuery({
      queryKey: ["rows", start, end],
      staleTime: Infinity,
      queryFn: () => api.fetchRows(start, end, signal),
    }),
});

The flow in words: viewport → range ([start, end)) → query key → sparse rows. The query key is the range, so two overlapping scrolls to the same range share one fetch, and the cache entry survives for the session. staleTime: Infinity is what keeps a revisited range from refetching, because the default (0) marks data stale immediately.

Why not useInfiniteQuery

useInfiniteQuery fetches pages sequentially from page 0. A virtualized grid asks for arbitrary ranges. Row 9,000 before row 50 exists. So the imperative fetchQuery is the fitting seam: same cache, same dedupe, no page-ordering constraint.

Behavior reference

  • Plain fetcher: the baseline without a cache
  • SWR: the same pattern through SWR's global cache

On this page