react-massive-table
v1.6.6
Published
High-performance, virtualized React table for massive datasets. Ships with column resizing, drag-reorder, multi-sort, and an optional group-by bar. Light/dark themes included via CSS variables and CSS Modules.
Readme
React Massive Table
High-performance, virtualized React table for massive datasets. Ships with column resizing, drag-reorder, multi-sort, and an optional group-by bar. Light/dark themes included via CSS variables and CSS Modules.
Why This
- Massive data: Renders visible rows only, with overscan, and avoids browser scroll height limits by chunking large scroll ranges.
- Pluggable data source: You provide
getRows(start, end, req)that can stream from memory, an API, IndexedDB, etc. - UX features built-in: Sort toggles, column drag-reorder, resize grips, row click handling, and an optional "Group by" drop zone.
- Theming: Light/dark presets and token overrides via CSS variables.
- TypeScript first: Full types for rows, columns, and events.
Installation
npm i react-massive-tablePeer deps: react@>=18, react-dom@>=18
Optional CSS file: If your bundler does not auto-inject CSS from ESM, import react-massive-table/styles.css once in your app entry.
Quick Start
Minimal, client-side rows from an in-memory array:
import { MassiveTable } from 'react-massive-table'
type Row = { id: number; name: string; value: number }
const data: Row[] = Array.from({ length: 100_000 }, (_, i) => ({
id: i + 1,
name: `Item ${i + 1}`,
value: Math.round(Math.random() * 1000),
}))
const columns = [
{ path: ['id'], title: 'ID', width: 80, align: 'right' },
{ path: ['name'], title: 'Name', width: 240 },
{ path: ['value'], title: 'Value', width: 140, align: 'right' },
]
function App() {
const getRows = (start: number, end: number) => ({
rows: data.slice(start, end),
total: data.length,
})
return (
<MassiveTable<Row>
getRows={getRows}
rowCount={data.length}
columns={columns}
style={{ height: '70vh', width: '100%' }}
/>
)
}Core Concepts
- Virtualization: You provide
rowCountandgetRows(start, end, req). The table asks for the visible window plusoverscanand renders only those rows.rowHeightcan be a number, a CSS length string, or'auto'to measure the first row. - Columns by path: Each column defines a
pathas an array to access nested values (e.g.['stats', 'score']). Provide arenderfunction for custom cells. - Sorting: Click a header to cycle none → asc → desc. Click another header to add a multi-sort. Controlled and uncontrolled modes supported via
sorts,defaultSorts, andonSortsChange. - Reorder & resize: Drag header cells to reorder columns. Resize via the grip at the right edge of each header (respects optional
minWidth). - Grouping (optional): Enable the group bar to drag headers into it and build nested groups. Control programmatically via
groupBy,onGroupByChange, andexpandedKeys. - Theming: Switch
mode='light' | 'dark'for built-ins, or pass athemetoken object to override colors, paddings, and radii.
API Reference
MassiveTable<Row>(props)
Required Props
getRows(start, end, req): Returns eitherRow[]or{ rows: Row[]; total: number }.reqcontains currentsorts,groupBy, and{ expandedKeys }for servers that flatten grouped results.rowCount: number: Total row count (or the current flattened total when grouping server-side).columns: ColumnDef<Row>[]: Column definitions.
Optional Props
rowHeight?: number | string | 'auto': Default'auto'. Numeric pixels, CSS length, or auto-measure first row.overscan?: number: Default10. Extra rows above/below viewport to keep scrolling smooth.mode?: 'light' | 'dark': Default'light'. Chooses the preset theme.theme?: Partial<Theme>: Optional token overrides (CSS variables) for fine-tuning visuals.style?: React.CSSProperties,className?: string: Container styling.enableSort?: boolean: Defaulttrue.enableReorder?: boolean: Defaulttrue.enableResize?: boolean: Defaulttrue.showGroupByDropZone?: boolean: Defaulttrue.
Event Handlers
onVisibleRangeChange?(start, end): Fires during scroll/range updates.onRowsRendered?(start, end): Fires after a fetch/render completes for a range.onRowClick?(row, rowIndex): Row click handler.onColumnOrderPreviewChange?(order, columns): Live updates while dragging headers.onColumnOrderChange?(order, columns): Final order after drop or cancel.
Controlled Props
- Controlled sorting:
sorts,defaultSorts,onSortsChange. - Controlled grouping:
groupBy,defaultGroupBy,onGroupByChange. - Controlled expansion:
expandedKeys,defaultExpandedKeys,onExpandedKeysChange.
ColumnDef<Row>
path: (string | number)[]: Required. Access path into your row object.title: string: Required. Header label.width?: number: Initial width in px.minWidth?: number: Minimum width in px.align?: 'left' | 'center' | 'right': Cell text alignment.headerTooltip?: string: Optional title tooltip for the header.render?(value, row, rowIndex): Custom cell renderer (return React nodes).
getRows Request Shape
RowsRequest<Row> passed to getRows(start, end, req):
{
sorts?: { path: ColumnPath; dir: 'asc' | 'desc' }[]
groupBy?: { path: ColumnPath }[]
groupState?: { expandedKeys: string[] }
}If you provide grouped data, emit a flattened stream with "group header" rows when appropriate and respect expandedKeys for expansion. A group header has the shape:
{ __group: true, key: string, depth?: number, value?: unknown, count?: number, path?: ColumnPath }Grouping Example (client-side)
The demo shows a client-side implementation that sorts, builds a group tree, and flattens it according to expandedKeys. For large data, prefer doing this on the server and returning a flattened slice plus total.
Theming
Built-ins: mode='light' | 'dark' set sensible defaults via CSS variables.
Token overrides (pass theme):
bg,color,headerBg,headerColorrowHoverBg,rowHoverColor,rowStripeBgborderColor,scrollbarThumb,scrollbarTrackradius,headerHeight,cellPxY,cellPxX,headerCellPyheaderShadow,focusRing,dimOverlay
Example:
<MassiveTable
getRows={getRows}
rowCount={rowCount}
columns={columns}
mode="dark"
theme={{ radius: '8px', headerHeight: '44px' }}
/>Accessibility
- Headers are buttons with clear labels and small sort indicators.
- Rows are focusable and can be activated with Enter/Space when
onRowClickis provided. - Group toggles have accessible labels for expand/collapse.
Performance Tips
- Keep
getRowsreferentially stable when possible; the component guards against stale effects, but stable references avoid extra work. - Cache or memoize server calls by
(start, end, sortsSig, groupSig)if you expect repeated ranges. - Provide consistent
columnsidentity: this component preserves column order state across renders and resets only when the set of columnpaths changes. - Use a fixed numeric
rowHeightwhen rows are uniform.'auto'measures the first row; for highly variable heights, prefer a fixed height for smooth scrolling.
Testing & Demo
The repo contains tests covering basic rendering, sorting, drag-reorder, resize, and grouping behaviors.
A demo app (src/App.tsx) generates deterministic data, showcases grouping, and toggles between light/dark modes.
FAQ
- Do I have to use the group bar? No; set
showGroupByDropZone={false}. - Can I control sorting/grouping externally? Yes—use the controlled props and their
on*Changecallbacks. - Do I need to import CSS? Most bundlers will include CSS emitted by the ES module automatically; if not, import
react-massive-table/styles.cssonce.
License
MIT
