Skip to content

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 showHow user interacts with rows
Sorting, filtering, groupingKeyboard navigation
Column visibility, orderingCell selection
PaginationInline 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.

Released under the MIT License.