TanStack Table Integration
Complete guide to integrating @ts-zen/react-datagrid with TanStack Table.
Overview
@ts-zen/react-datagrid works alongside TanStack Table through parallel composition. Both libraries operate on the same data but handle orthogonal concerns:
| TanStack Table | @ts-zen/react-datagrid |
|---|---|
| Which rows to show | How user interacts with rows |
| Sorting, filtering, grouping | Keyboard navigation |
| Column visibility, ordering | Cell selection |
| Pagination | Inline editing |
Basic Setup
tsx
import {
useReactTable,
getCoreRowModel,
flexRender,
} from "@tanstack/react-table";
import { useGridBehavior, useCellBehavior } from "@ts-zen/react-datagrid";
function DataGrid({ data, columns }) {
// TanStack Table handles data logic
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
// @ts-zen handles behavior
const grid = useGridBehavior({
rows: table.getRowModel().rows,
columns: table.getVisibleLeafColumns(),
});
return (
<div {...grid.props} role="grid">
{/* Header row */}
<div role="row">
{table
.getHeaderGroups()
.map((headerGroup) =>
headerGroup.headers.map((header) => (
<HeaderCell key={header.id} grid={grid} header={header} />
)),
)}
</div>
{/* Data rows */}
{table.getRowModel().rows.map((row) => (
<div key={row.id} role="row">
{row.getVisibleCells().map((cell) => (
<DataCell key={cell.id} grid={grid} cell={cell} />
))}
</div>
))}
</div>
);
}
function HeaderCell({ grid, header }) {
const behavior = useHeaderBehavior(grid, header.column.id);
return (
<div {...behavior.props} role="columnheader">
{flexRender(header.column.columnDef.header, header.getContext())}
</div>
);
}
function DataCell({ grid, cell }) {
const behavior = useCellBehavior(grid, cell.row.id, cell.column.id);
return (
<div
{...behavior.props}
role="gridcell"
className={cn(
behavior.isFocused && "ring-2 ring-blue-500",
behavior.isSelected && "bg-blue-50",
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
);
}With Sorting
When TanStack Table sorts data, focus follows the row ID (not the visual position):
tsx
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: { sorting },
onSortingChange: setSorting,
});
// Grid behavior automatically adapts to new row order
const grid = useGridBehavior({
rows: table.getRowModel().rows, // Already sorted
columns: table.getVisibleLeafColumns(),
});With Filtering
Filtered-out rows are excluded from navigation:
tsx
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
});
const grid = useGridBehavior({
rows: table.getRowModel().rows, // Only visible rows
columns: table.getVisibleLeafColumns(),
});When a focused row is filtered out, focus auto-recovers to the nearest valid row.
With Column Visibility
Hidden columns are excluded from navigation:
tsx
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: { columnVisibility },
onColumnVisibilityChange: setColumnVisibility,
});
const grid = useGridBehavior({
rows: table.getRowModel().rows,
columns: table.getVisibleLeafColumns(), // Only visible columns
});With Pinned Columns
Pinned columns appear in visual order:
tsx
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: { columnPinning },
});
// getVisibleLeafColumns() returns columns in visual order
// (left pinned, center, right pinned)
const grid = useGridBehavior({
rows: table.getRowModel().rows,
columns: table.getVisibleLeafColumns(),
});Editable Cells
Combine TanStack Table's data management with @ts-zen's edit lifecycle:
tsx
function EditableCell({ grid, cell }) {
const behavior = useCellBehavior(grid, cell.row.id, cell.column.id, {
type: "editable",
});
const value = cell.getValue();
if (behavior.isEditing) {
return (
<div {...behavior.props}>
<input
autofocus
defaultValue={value}
onBlur={(e) => {
// Update TanStack Table's data
updateData(cell.row.id, cell.column.id, e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
updateData(cell.row.id, cell.column.id, e.currentTarget.value);
}
if (e.key === "Escape") {
behavior.cancelEdit();
}
}}
/>
</div>
);
}
return (
<div {...behavior.props} className={behavior.isFocused && "ring-2"}>
{value}
</div>
);
}With Virtualization
@ts-zen works with virtualization libraries. Ensure proper overscan for keyboard navigation:
tsx
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualizedGrid({ table, grid }) {
const parentRef = useRef(null);
const rowVirtualizer = useVirtualizer({
count: table.getRowModel().rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
overscan: 3, // Important: at least 1 for keyboard nav
});
return (
<div ref={parentRef} style={{ height: 400, overflow: "auto" }}>
<div style={{ height: rowVirtualizer.getTotalSize() }}>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const row = table.getRowModel().rows[virtualRow.index];
return (
<div
key={row.id}
style={{
position: "absolute",
top: virtualRow.start,
height: virtualRow.size,
}}
>
{row.getVisibleCells().map((cell) => (
<DataCell key={cell.id} grid={grid} cell={cell} />
))}
</div>
);
})}
</div>
</div>
);
}Complete Example
See the Storybook examples for complete working implementations.