Tablecraft
API Reference

useVirtualRows

Render only the rows visible in the viewport, for large datasets where rendering every row causes lag.

Render only the rows visible in the viewport — for large datasets where rendering all rows at once causes lag. Requires @tanstack/react-virtual:

npm install @tanstack/react-virtual

Signature

function useVirtualRows<TData extends RowData>(
  table: Table<TData>,
  options: VirtualRowsOptions
): VirtualRowsReturn<TData>

Options

OptionTypeDefaultDescription
rowHeightnumber— (required)Fixed height of every row in pixels
overscannumber5Extra rows rendered above and below the visible area to prevent scroll flicker on fast scrolling

Return

PropertyTypeDescription
virtualRowsVirtualRow<TData>[]Only the rows currently visible in the viewport
totalHeightnumberTotal scroll height in px — set as the height of the inner wrapper div
containerRefRefObject<HTMLDivElement>Attach to the outer scroll container div
scrollToIndex(index: number) => voidProgrammatically scroll to any row by its position in the full rows array

Each VirtualRow<TData>:

FieldTypeDescription
rowRow<TData>Full TanStack Row — row.original, row.id, row.getVisibleCells(), etc.
indexnumberPosition in the full rows array
startnumberPixel offset from top — use as top in absolute positioning
sizenumberRow height in px (always equals rowHeight in fixed mode)

Usage

Note: Pass pagination: false to useTable when combining it with useVirtualRows. useTable paginates to a default page size of 10, so without disabling it the virtualizer only ever sees ~10 rows instead of your full dataset.

import { useTable } from '@marvinackerman/tablecraft'
import { useVirtualRows } from '@marvinackerman/tablecraft/virtual'
import { flexRender } from '@tanstack/react-table'

const { table } = useTable({ data, columns, pagination: false })

const { virtualRows, totalHeight, containerRef, scrollToIndex } = useVirtualRows(table, {
  rowHeight: 48,
})

// Virtualized tables use div-based rendering — position: absolute requires it.
// Use role="row" and role="cell" for screen reader compatibility.
<div ref={containerRef} style={{ height: 600, overflow: 'auto' }}>
  <div style={{ height: totalHeight, position: 'relative' }}>
    {virtualRows.map(({ row, start, size }) => (
      <div
        key={row.id}
        role="row"
        style={{ position: 'absolute', top: start, height: size, width: '100%' }}
      >
        {row.getVisibleCells().map(cell => (
          <div key={cell.id} role="cell">
            {flexRender(cell.column.columnDef.cell, cell.getContext())}
          </div>
        ))}
      </div>
    ))}
  </div>
</div>

// Programmatic scroll:
scrollToIndex(42)

Live example

10,000 in-memory rows, rendered with useVirtualRows inside a fixed-height scroll container — only the rows in view (plus overscan) are ever mounted.

ID
Name
Value

10,000 rows generated in memory — only the rows visible in the 400px viewport (plus overscan) are mounted at any time. Scroll to see rows mount and unmount.

Notes

  • Standard <table>/<tbody>/<tr> markup is incompatible with virtualization because browsers do not support position: absolute inside <tbody>. Use <div role="table">, <div role="row">, and <div role="cell"> — semantically equivalent for screen readers.
  • Works with useTable, useQueryTable, and useInfiniteTable equally.

On this page