Claude Code for TanStack Table: Advanced Data Grids in React — Claude Skills 360 Blog
Blog / Frontend / Claude Code for TanStack Table: Advanced Data Grids in React
Frontend

Claude Code for TanStack Table: Advanced Data Grids in React

Published: November 14, 2026
Read time: 8 min read
By: Claude Skills 360

TanStack Table is a headless table library — it handles all the logic (sorting, filtering, pagination, row selection, column pinning) but renders nothing, leaving you full control over markup and styles. The v8 API is pure TypeScript: define columns with columnHelper.accessor(), pass data and config to useReactTable(), get back a table instance you render however you want. Claude Code writes TanStack Table column definitions, feature hooks, server-side sorting/filtering integration, and the TanStack Virtual integration for rendering 100,000-row grids without layout shifts.

CLAUDE.md for TanStack Table Projects

## Table Stack
- TanStack Table v8 with React (headless — bring your own styles)
- Styling: Tailwind for table cells; sticky headers via CSS
- Server-side: sorting/filtering/pagination state → API query params
- Virtualization: @tanstack/react-virtual for >500 row tables
- Column visibility: persisted to localStorage
- Row selection: checkboxes with indeterminate state for select-all
- Export: react-csv or xlsx for client-side export

Column Definitions

// tables/orders/columns.tsx
import { createColumnHelper, type ColumnDef } from '@tanstack/react-table';
import { format } from 'date-fns';
import { OrderStatusBadge } from '../components/OrderStatusBadge';

export type OrderRow = {
  id: string;
  createdAt: Date;
  customerName: string;
  status: 'pending' | 'shipped' | 'delivered' | 'cancelled';
  totalCents: number;
  itemCount: number;
};

const col = createColumnHelper<OrderRow>();

export const orderColumns: ColumnDef<OrderRow, any>[] = [
  // Row selection checkbox
  {
    id: 'select',
    header: ({ table }) => (
      <input
        type="checkbox"
        checked={table.getIsAllPageRowsSelected()}
        indeterminate={table.getIsSomePageRowsSelected() ? true : undefined}
        onChange={table.getToggleAllPageRowsSelectedHandler()}
        className="rounded border-gray-300"
      />
    ),
    cell: ({ row }) => (
      <input
        type="checkbox"
        checked={row.getIsSelected()}
        onChange={row.getToggleSelectedHandler()}
        className="rounded border-gray-300"
      />
    ),
    enableSorting: false,
    size: 40,
  },
  
  col.accessor('id', {
    header: 'Order ID',
    cell: info => (
      <a href={`/orders/${info.getValue()}`} className="font-mono text-blue-600 hover:underline">
        #{info.getValue().slice(0, 8)}
      </a>
    ),
    enableSorting: false,
  }),
  
  col.accessor('createdAt', {
    header: 'Date',
    cell: info => format(info.getValue(), 'MMM d, yyyy HH:mm'),
    sortingFn: 'datetime',
  }),
  
  col.accessor('customerName', {
    header: 'Customer',
    filterFn: 'includesString',
  }),
  
  col.accessor('status', {
    header: 'Status',
    cell: info => <OrderStatusBadge status={info.getValue()} />,
    filterFn: (row, columnId, filterValue: string[]) =>
      filterValue.length === 0 || filterValue.includes(row.getValue(columnId)),
  }),
  
  col.accessor('totalCents', {
    header: 'Total',
    cell: info => `$${(info.getValue() / 100).toFixed(2)}`,
    sortingFn: 'basic',
    meta: { align: 'right' },
  }),
  
  col.accessor('itemCount', {
    header: 'Items',
    meta: { align: 'right' },
  }),
];

Table Component with All Features

// tables/orders/OrderTable.tsx
import {
  useReactTable, getCoreRowModel, getSortedRowModel,
  getFilteredRowModel, getPaginationRowModel,
  flexRender, type SortingState, type ColumnFiltersState, type RowSelectionState,
} from '@tanstack/react-table';
import { useState } from 'react';
import { orderColumns } from './columns';

interface OrderTableProps {
  data: OrderRow[];
  onSelectionChange?: (selectedIds: string[]) => void;
}

export function OrderTable({ data, onSelectionChange }: OrderTableProps) {
  const [sorting, setSorting] = useState<SortingState>([
    { id: 'createdAt', desc: true }
  ]);
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
  const [columnVisibility, setColumnVisibility] = useState({
    itemCount: false,  // Hidden by default
  });
  
  const table = useReactTable({
    data,
    columns: orderColumns,
    state: { sorting, columnFilters, rowSelection, columnVisibility },
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    onRowSelectionChange: (updater) => {
      const next = typeof updater === 'function' ? updater(rowSelection) : updater;
      setRowSelection(next);
      onSelectionChange?.(Object.keys(next).filter(k => next[k]));
    },
    onColumnVisibilityChange: setColumnVisibility,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    initialState: { pagination: { pageSize: 25 } },
  });
  
  return (
    <div className="space-y-2">
      {/* Toolbar */}
      <div className="flex items-center gap-2">
        <input
          placeholder="Filter customers..."
          value={(table.getColumn('customerName')?.getFilterValue() as string) ?? ''}
          onChange={e => table.getColumn('customerName')?.setFilterValue(e.target.value)}
          className="h-8 w-64 rounded border border-gray-300 px-2 text-sm"
        />
        <span className="text-sm text-gray-500">
          {table.getFilteredRowModel().rows.length} results
          {Object.keys(rowSelection).length > 0 && ` · ${Object.keys(rowSelection).length} selected`}
        </span>
      </div>
      
      {/* Table */}
      <div className="overflow-x-auto rounded border border-gray-200">
        <table className="w-full text-sm">
          <thead className="sticky top-0 bg-gray-50 border-b">
            {table.getHeaderGroups().map(headerGroup => (
              <tr key={headerGroup.id}>
                {headerGroup.headers.map(header => (
                  <th
                    key={header.id}
                    style={{ width: header.getSize() }}
                    className="px-3 py-2 text-left font-medium text-gray-700 whitespace-nowrap"
                    onClick={header.column.getToggleSortingHandler()}
                  >
                    {flexRender(header.column.columnDef.header, header.getContext())}
                    {{ asc: ' ↑', desc: ' ↓' }[header.column.getIsSorted() as string] ?? ''}
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody>
            {table.getRowModel().rows.map(row => (
              <tr key={row.id} className="border-b hover:bg-gray-50">
                {row.getVisibleCells().map(cell => (
                  <td
                    key={cell.id}
                    className="px-3 py-2 text-gray-900"
                    style={{ textAlign: (cell.column.columnDef.meta as any)?.align }}
                  >
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      
      {/* Pagination */}
      <div className="flex items-center justify-between text-sm text-gray-700">
        <div>
          Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
        </div>
        <div className="flex gap-1">
          <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} className="px-2 py-1 rounded border disabled:opacity-50">←</button>
          <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()} className="px-2 py-1 rounded border disabled:opacity-50">→</button>
        </div>
      </div>
    </div>
  );
}

Server-Side Sorting and Filtering

// tables/orders/ServerOrderTable.tsx — state → API params pattern
import { useQuery } from '@tanstack/react-query';

function useServerOrders(sorting: SortingState, filters: ColumnFiltersState, page: number) {
  const params = new URLSearchParams({
    page: String(page),
    pageSize: '25',
    ...(sorting[0] && {
      sortBy: sorting[0].id,
      sortDir: sorting[0].desc ? 'desc' : 'asc',
    }),
    ...Object.fromEntries(filters.map(f => [f.id, String(f.value)])),
  });
  
  return useQuery({
    queryKey: ['orders', params.toString()],
    queryFn: () => fetch(`/api/orders?${params}`).then(r => r.json()),
    keepPreviousData: true,  // Avoid flash on page change
  });
}

For the React Query data fetching that drives server-side table state, the React Query guide covers query keys, caching, and optimistic updates. For the React 19 Server Components approach that can replace client-side tables for some use cases, the React 19 guide covers RSC data loading patterns. The Claude Skills 360 bundle includes TanStack Table skill sets covering column definitions, sorting/filtering, server-side integration, and row virtualization. Start with the free tier to try data grid column generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free