Virtualized React data grid

Virtualized React data grid for fast viewport rendering

A virtualized React data grid renders a moving window of rows and columns. Correct implementation depends on stable geometry, row identity, overscan, focus restoration, and measured cell cost.

Open virtualization API Read the performance guide

Start with stable geometry

Give the grid a bounded height and predictable row and column dimensions. Variable heights increase measurement and scroll-position complexity. Use stable row IDs so recycled DOM never changes record identity.

Tune overscan from interaction evidence

Too little overscan can expose blank regions during fast scroll; too much recreates the DOM cost virtualization is meant to avoid. Test trackpad, wheel, scrollbar drag, Page Down, and keyboard navigation on target hardware.

Profile the expensive path

Use browser performance tools to inspect scripting, style, layout, paint, memory, and React commits. Optimize custom cell renderers and data transforms before increasing buffers. Re-test editing and focus after each tuning change.

Keep focus and selection logical

Store active cell and selection by stable row and column identity rather than DOM references. When elements are recycled, restore focus intentionally and announce relevant state changes. Test selection ranges that cross unloaded or unrendered areas. Custom inputs, menus, and detail panels require additional focus rules because they may outlive the cell that opened them.

Automate regression checks

Create a repeatable dataset and scripted tasks for scroll, keyboard movement, editing, selection, filtering, and resizing. Track interaction timing and browser errors across releases. Screenshot tests can catch alignment problems, but they do not prove focus or data correctness. Include assertions that edits and selections remain attached to the expected row after recycling.

Assert data correctness after recycling

A virtualized grid reuses DOM nodes, so tests must prove that edits, selection, focus, and validation remain attached to the correct row after scrolling. Use stable row IDs in assertions. A grid that scrolls smoothly but edits the wrong record is a correctness failure, not a performance success.

Understand why screenshots are weak evidence

A screenshot can prove the grid painted, but it cannot prove scroll stability, focus restoration, row identity, or edit correctness after recycling. Use interaction tests and performance traces as evidence.

Handle pinned and interactive cells

Pinned rows and columns, menus, tooltips, editors, validation messages, and detail content can follow different rendering paths from ordinary cells. Test focus transfer, clipping, scroll synchronization, and cleanup when their source cell leaves the viewport. Expensive custom cells should avoid data fetching or repeated computation during render.

Choose a loading strategy separately

Viewport virtualization does not decide when data is fetched. Use an in-memory working set when client operations remain affordable, infinite loading for sequential discovery, or a server row model when remote sorting, filtering, grouping, paging, or totals are required. Test each loading state without changing the rendering benchmark so bottlenecks remain attributable.

Document variable-height constraints

If rows can change height, test measurement, scroll anchoring, focus, content expansion, and updates after images or asynchronous content load. Fixed geometry is easier to virtualize reliably. Use variable height only when the user value justifies the additional measurement and regression work.

Virtualized React data grid implementation

import { Grid } from "@ace-grid/core";

export function VirtualizedOrdersGrid({ rows, columns, loadMoreRows }) {
  return (
    <Grid
      data={{ rows, columns }}
      columns={{ columnWidths: {}, fillWidth: true }}
      layout={{ width: 1200, height: 600 }}
      virtual={{
        enableVirtualization: true,
        enableHorizontalVirtualization: true,
        enableCellContentVirtualization: true,
      }}
      scroll={{
        enableInfiniteScroll: true,
        infiniteScrollBatchSize: 100,
        loadMoreRows,
      }}
    />
  );
}

Sources