SWR
Lazy ranges through SWR's preload. One fetch per range key, shared with any useSWR consumer.
SWR has no queryClient.fetchQuery. preload(key, fetcher) is its imperative entry point.
It runs the fetcher once per key, hands back its promise, and stores the result in the
global cache. So a second grid or a detail panel on the same page that calls useSWR with
the same key gets the range for free.
pnpm dlx shadcn@latest add @gridcn/data-grid-lazy-swr-demo
The seam
const lazy = useDataGridLazyRows<Row>({
total: 10_000,
getRowId: (row) => row.id,
fetchRows: (start, end, signal) =>
preload(["rows", start, end] as [string, number, number], (key) =>
api.fetchRows(key[1], key[2], signal),
),
});The fetcher receives the key itself as its argument (the SWR 2.x convention. useSWR
works the same way), hence the tuple cast rather than destructured parameters. Concurrent
requests for the same range share one fetch, and the cached range survives for the
session. Scrolling back never refetches.
Revalidate on focus
useSWR components revalidate on window focus and reconnect by default. The lazy ranges
here are plain cache entries consumed through the hook, so they stay put for the session (
no focus-time churn while you scroll). If you also mount useSWR consumers on the same keys,
decide once whether you want that focus revalidation (revalidateOnFocus: false) or rely on
it as a cheap freshness check.
Behavior reference
- Lazy loading: the
useDataGridLazyRowscontract
Related
- Plain fetcher: the baseline without a cache
- TanStack Query: the same pattern through a
fetchQuery