gridcn1.2.2

Plain fetcher

useDataGridLazyRows with a plain simulated-latency fetcher, as the baseline pattern.

The baseline: useDataGridLazyRows owns the sparse row array and the range bookkeeping. You own fetchRows(start, end, signal). The demo has 100,000 rows. Only the ranges that actually scroll into view are ever fetched, and skeleton rows hold the space until each range lands. The two failure buttons show the transient/permanent error split. The third page covers what a real fetcher replaces.

0 / 100,000 rows loaded
ID
Name
Email
Score
pnpm dlx shadcn@latest add @gridcn/data-grid-lazy-demo

The seam

const lazy = useDataGridLazyRows<Row>({
  total: 100_000,
  getRowId: (row) => row.id,
  fetchRows: (start, end, signal) => api.fetchRows(start, end, signal),
  onError: (error, range) => {
    /* transient: auto-retries on next scroll; permanent: surface a retry button */
  },
});
  • fetchRows resolves exactly the [start, end) slice, positionally aligned to start. The hook writes it into the sparse array. The core renders real rows over the skeletons.
  • signal aborts the in-flight fetch when the range leaves view. A rejection after abort is ignored, so an abort is not an error. Reject with an AbortError and move on.
  • Failed ranges revert to unloaded and the next scroll into them retries automatically ("refetch on next visibility"). onError still fires, so a permanent failure (an expired token, for example) can surface a manual retry. Re-fire gridProps.onRenderedRowsChange(range), and the range takes the same path as a real scroll.

Bring your own fetcher

The two library pages wire the same seam to a cache so fetched ranges survive the rest of the session: TanStack Query and SWR.

Behavior reference

  • Large data: the opposite pattern: everything loaded, virtualization does the work

On this page