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-virtualSignature
function useVirtualRows<TData extends RowData>(
table: Table<TData>,
options: VirtualRowsOptions
): VirtualRowsReturn<TData>Options
| Option | Type | Default | Description |
|---|---|---|---|
rowHeight | number | — (required) | Fixed height of every row in pixels |
overscan | number | 5 | Extra rows rendered above and below the visible area to prevent scroll flicker on fast scrolling |
Return
| Property | Type | Description |
|---|---|---|
virtualRows | VirtualRow<TData>[] | Only the rows currently visible in the viewport |
totalHeight | number | Total scroll height in px — set as the height of the inner wrapper div |
containerRef | RefObject<HTMLDivElement> | Attach to the outer scroll container div |
scrollToIndex | (index: number) => void | Programmatically scroll to any row by its position in the full rows array |
Each VirtualRow<TData>:
| Field | Type | Description |
|---|---|---|
row | Row<TData> | Full TanStack Row — row.original, row.id, row.getVisibleCells(), etc. |
index | number | Position in the full rows array |
start | number | Pixel offset from top — use as top in absolute positioning |
size | number | Row height in px (always equals rowHeight in fixed mode) |
Usage
Note: Pass
pagination: falsetouseTablewhen combining it withuseVirtualRows.useTablepaginates 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.
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 supportposition: absoluteinside<tbody>. Use<div role="table">,<div role="row">, and<div role="cell">— semantically equivalent for screen readers. - Works with
useTable,useQueryTable, anduseInfiniteTableequally.