| name | tanstack-table |
| description | TanStack Table v8 headless data tables with server-side features for Cloudflare Workers + D1. Use for pagination, filtering, sorting, virtualization, or encountering state management, TanStack Query coordination, URL sync errors. |
| license | MIT |
| allowed-tools | ["Bash","Read","Write","Edit"] |
| metadata | {"version":"1.1.0","author":"Claude Skills Maintainers","last-verified":"2025-12-09T00:00:00.000Z","production-tested":true,"keywords":["tanstack table","react table","data table","datagrid","headless table","server-side pagination","server-side filtering","server-side sorting","tanstack query integration","cloudflare d1","cloudflare workers","virtualization","tanstack virtual","large datasets","table state management","url state sync","column configuration","typescript table","react table v8","headless ui","data visualization","pagination","filtering","sorting"]} |
TanStack Table Skill
Build production-ready, headless data tables with TanStack Table v8, optimized for server-side patterns and Cloudflare Workers integration.
When to Use This Skill
Auto-triggers when you mention:
- "data table" or "datagrid"
- "server-side pagination" or "server-side filtering"
- "TanStack Table" or "React Table"
- "table with large dataset"
- "paginate/filter/sort with API"
- "Cloudflare D1 table integration"
- "virtualize table" or "large list performance"
Use this skill when:
- Building data tables with pagination, filtering, or sorting
- Implementing server-side table features (API-driven)
- Integrating tables with TanStack Query for data fetching
- Working with large datasets (1000+ rows) needing virtualization
- Connecting tables to Cloudflare D1 databases
- Need headless table logic without opinionated UI
- Migrating from other table libraries to TanStack Table v8
What This Skill Provides
1. Production Templates (7)
- Basic client-side table - Simple table with local data
- Server-paginated table - API-driven pagination with TanStack Query
- D1 database integration - Cloudflare D1 + Workers API + Table
- Column configuration patterns - Type-safe column definitions
- Controlled table state - Column visibility, pinning, ordering, fuzzy/global filtering, row selection
- Virtualized large dataset - Performance optimization with TanStack Virtual
- shadcn/ui styled table - Integration with Tailwind v4 + shadcn
2. Server-Side Patterns
- Pagination with API backends
- Filtering with query parameters
- Sorting with database queries
- State management (page, filters, sorting)
- URL synchronization
- TanStack Query coordination
3. Cloudflare Integration
- D1 database query patterns
- Workers API endpoints for table data
- Pagination + filtering + sorting in SQL
- Bindings setup (wrangler.jsonc)
- Client-side integration patterns
4. Performance Optimization
- Virtualization with TanStack Virtual
- Large dataset rendering (10k+ rows)
- Memory-efficient patterns
- useVirtualizer() integration
5. Feature Controls & UX
- Column visibility toggles and pinning (frozen columns)
- Column ordering and sizing defaults
- Global + fuzzy search and faceted filters
- Row selection and row pinning patterns
- Controlled state checklist to avoid perf regressions
6. Error Prevention
Documents and prevents 6+ common issues:
- Server-side state management confusion
- TanStack Query integration errors (query key coordination)
- Column filtering with API backends
- Manual sorting setup mistakes
- URL state synchronization issues
- Large dataset performance problems
- Over-controlling table state (columnSizingInfo) causing extra renders
Quick Start
Installation
bun add @tanstack/react-table@latest
bun add @tanstack/react-virtual@latest
bun add @tanstack/match-sorter-utils@latest
Latest verified versions (as of 2025-12-09):
@tanstack/react-table: v8.21.3 (stable)
@tanstack/react-virtual: v3.13.23
@tanstack/match-sorter-utils: v8.21.3 (for fuzzy filtering)
React support: Works on React 16.8+ through React 19; React Compiler is not supported.
Basic Client-Side Table
import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
interface User {
id: string
name: string
email: string
}
const columns: ColumnDef<User>[] = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
]
function UsersTable() {
const data = useMemo<User[]>(() => [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
], [])
const table = useReactTable({
data,
columns,
getCoreRowModel: (),
})
(
)
}
Server-Side Patterns (Recommended for Large Datasets)
Pattern 1: Server-Side Pagination with TanStack Query
Cloudflare Workers API Endpoint:
import { Env } from '../../types'
export async function onRequestGet(context: { request: Request; env: Env }) {
const url = new URL(context.request.url)
const page = Number(url.searchParams.get('page')) || 0
const pageSize = Number(url.searchParams.get('pageSize')) || 20
const offset = page * pageSize
const { results, meta } = await context.env.DB.prepare(`
SELECT id, name, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`).bind(pageSize, offset).all()
const countResult = await context.env.DB.prepare(`
SELECT COUNT(*) as total FROM users
`).first<{ total: number }>()
return .({
: results,
: {
page,
pageSize,
: countResult?. || ,
: .((countResult?. || ) / pageSize),
},
})
}
Client-Side Table with TanStack Query:
import { useReactTable, getCoreRowModel, PaginationState } from '@tanstack/react-table'
import { useQuery } from '@tanstack/react-query'
import { useState } from 'react'
function ServerPaginatedTable() {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 20,
})
const { data, isLoading } = useQuery({
queryKey: ['users', pagination.pageIndex, pagination.pageSize],
queryFn: async () => {
const response = await fetch(
`/api/users?page=${pagination.pageIndex}&pageSize=${pagination.pageSize}`
)
return response.json()
},
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
: data?.. ?? ,
: { pagination },
: setPagination,
})
(isLoading)
(
)
}
Pattern 2: Server-Side Filtering
API with Filter Support:
export async function onRequestGet(context: { request: Request; env: Env }) {
const url = new URL(context.request.url)
const search = url.searchParams.get('search') || ''
const { results } = await context.env.DB.prepare(`
SELECT * FROM users
WHERE name LIKE ? OR email LIKE ?
LIMIT 20
`).bind(`%${search}%`, `%${search}%`).all()
return Response.json({ data: results })
}
Client-Side:
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const { data } = useQuery({
queryKey: ['users', columnFilters],
queryFn: async () => {
const search = columnFilters.find(f => f.id === 'search')?.value || ''
return fetch(`/api/users?search=${search}`).then(r => r.json())
},
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualFiltering: true,
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
})
Virtualization for Large Datasets
For 1000+ rows, use TanStack Virtual to only render visible rows:
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
function VirtualizedTable() {
const tableContainerRef = useRef<HTMLDivElement>(null)
const table = useReactTable({
data: largeDataset,
columns,
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 50,
overscan: 10,
})
return (
<div ref={tableContainerRef} style={{ height: '600px', overflow: 'auto' }}>
<table style={{ height: `${()}` }}>
{/* header */}
{rowVirtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
{row.getVisibleCells().map(cell => (
{cell.renderValue()}
))}
)
})}
)
}
Common Errors & Solutions
Error 1: Infinite Re-Renders
Problem: Table re-renders infinitely, browser freezes.
Cause: data or columns references change on every render.
Solution: Always use useMemo or useState:
function Table() {
const data = [{ id: 1 }]
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
function Table() {
const data = useMemo(() => [{ id: 1 }], [])
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
const data = [{ id: 1 }]
function Table() {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
Error 2: TanStack Query + Table State Mismatch
Problem: Query refetches but pagination state not in sync, causing stale data.
Solution: Include ALL table state in query key:
const { data } = useQuery({
queryKey: ['users'],
queryFn: () => fetch(`/api/users?page=${pagination.pageIndex}`).then(r => r.json())
})
const { data } = useQuery({
queryKey: ['users', pagination.pageIndex, pagination.pageSize, columnFilters, sorting],
queryFn: () => {
const params = new URLSearchParams({
page: pagination.pageIndex.toString(),
pageSize: pagination.pageSize.toString(),
})
return fetch(`/api/users?${params}`).then(r => r.json())
}
})
Error 3: Server-Side Features Not Working
Problem: Pagination/filtering/sorting doesn't trigger API calls.
Solution: Set manual* flags to true:
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
manualFiltering: true,
manualSorting: true,
pageCount: serverPageCount,
})
Error 4: TypeScript "Cannot Find Module" for Column Helper
Problem: Import errors for createColumnHelper.
Solution: Import from correct path:
import { createColumnHelper } from '@tanstack/table-core'
import { createColumnHelper } from '@tanstack/react-table'
const columnHelper = createColumnHelper<User>()
const columns = [
columnHelper.accessor('name', {
header: 'Name',
cell: info => info.getValue(),
}),
]
Error 5: Sorting Not Working with Server-Side
Problem: Clicking sort headers doesn't update data.
Solution: Include sorting in query key and API call:
const [sorting, setSorting] = useState<SortingState>([])
const { data } = useQuery({
queryKey: ['users', pagination, sorting],
queryFn: async () => {
const sortParam = sorting[0]
? `&sortBy=${sorting[0].id}&sortOrder=${sorting[0].desc ? 'desc' : 'asc'}`
: ''
return fetch(`/api/users?page=${pagination.pageIndex}${sortParam}`).then(r => r.json())
}
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualSorting: true,
state: { sorting },
onSortingChange: setSorting,
})
Error 6: Poor Performance with Large Datasets
Problem: Table slow/laggy with 1000+ rows.
Solution: Use virtualization (see example above) or implement server-side pagination.
Integration with Existing Skills
With tanstack-query Skill
TanStack Table + TanStack Query is the recommended pattern:
const { data, isLoading } = useQuery({
queryKey: ['users', tableState],
queryFn: fetchUsers,
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
})
With cloudflare-d1 Skill
export async function onRequestGet({ env }: { env: Env }) {
const { results } = await env.DB.prepare('SELECT * FROM users LIMIT 20').all()
return Response.json({ data: results })
}
const { data } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json())
})
With tailwind-v4-shadcn Skill
Use shadcn/ui Table components with TanStack Table logic:
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'
function StyledTable() {
const table = useReactTable({ })
return (
<Table>
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map(header => (
<TableHead key={header.id}>
{header.column.columnDef.header}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map(row => (
<TableRow key={row.id}>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>
{cell.renderValue()}
</TableCell>
))}
</TableRow>
))}
</TableBody>
)
}
Best Practices
1. Always Memoize Data and Columns
const data = useMemo(() => [...], [dependencies])
const columns = useMemo(() => [...], [])
2. Use Server-Side for Large Datasets
- Client-side: <1000 rows
- Server-side: 1000+ rows or frequently changing data
3. Coordinate Query Keys with Table State
queryKey: ['resource', pagination, filters, sorting]
4. Provide Loading States
if (isLoading) return <TableSkeleton />
if (error) return <ErrorMessage error={error} />
5. Use Column Helper for Type Safety
const columnHelper = createColumnHelper<YourType>()
const columns = [
columnHelper.accessor('field', { })
]
6. Virtualize Large Client-Side Tables
if (data.length > 1000) {
}
7. Control Only the State You Need
- Keep
sorting, pagination, filters, visibility, pinning, order, selection in controlled state when you must persist or sync.
- Avoid controlling
columnSizingInfo unless persisting drag state; it triggers frequent updates and can hurt performance.
Templates Reference
All templates available in ~/.claude/skills/tanstack-table/templates/:
- package.json - Dependencies and versions
- basic-client-table.tsx - Simple client-side table
- server-paginated-table.tsx - Server-side pagination with Query
- d1-database-example.tsx - Cloudflare D1 integration
- column-configuration.tsx - Type-safe column patterns
- controlled-table-state.tsx - Visibility, pinning, ordering, fuzzy/global filtering, selection
- virtualized-large-dataset.tsx - Performance with Virtual
- shadcn-styled-table.tsx - Tailwind v4 + shadcn UI styling
Reference Docs
Deep-dive guides in ~/.claude/skills/tanstack-table/references/:
- server-side-patterns.md - Pagination, filtering, sorting with APIs
- query-integration.md - Coordinating with TanStack Query
- cloudflare-d1-examples.md - Workers + D1 complete examples
- performance-virtualization.md - TanStack Virtual guide
- common-errors.md - All 6+ documented issues with solutions
- feature-controls.md - Controlled state, visibility, pinning, ordering, fuzzy/global filtering, selection
When to Load References
Claude should suggest loading these reference files based on user needs:
Load references/common-errors.md when:
- User encounters infinite re-renders or table freezing
- Query data not syncing with pagination state changes
- Server-side features (pagination/filtering/sorting) not triggering API calls
- TypeScript errors with column helper imports
- Sorting state changes not updating API calls
- Performance problems with 1000+ rows client-side
- Any error message mentioned in the 6 documented issues
Load references/server-side-patterns.md when:
- User asks about implementing pagination with API backends
- Need to build filtering with backend query parameters
- Implementing sorting tied to database queries
- Building Cloudflare Workers or any API endpoints for table data
- Coordinating table state (page, filters, sort) with server calls
- Questions about manualPagination, manualFiltering, or manualSorting flags
Load references/query-integration.md when:
- Coordinating TanStack Table + TanStack Query together
- Query keys and table state synchronization issues
- Refetch patterns when pagination/filter/sort changes
- Query key composition with table state
- Stale data issues with server-side tables
Load references/cloudflare-d1-examples.md when:
- Building Cloudflare Workers API endpoints for table data
- Writing D1 database queries with pagination/filtering
- Need complete end-to-end Cloudflare integration examples
- SQL query patterns for table features (LIMIT/OFFSET, WHERE, ORDER BY)
- wrangler.jsonc bindings setup for D1 + table
Load references/performance-virtualization.md when:
- Working with 1000+ row datasets client-side
- TanStack Virtual integration questions
- Memory-efficient rendering patterns
- useVirtualizer() hook usage
- Large table performance optimization
- Questions about row virtualization or scroll performance
Load references/feature-controls.md when:
- Need column visibility, pinning, or ordering controls
- Building toolbars (global search, toggles) or syncing state to URL/localStorage
- Implementing fuzzy/global search or faceted filters
- Setting up row selection/pinning or controlled pagination/sorting
Token Efficiency
Without this skill:
- ~8,000 tokens: Research v8 changes, server-side patterns, Query integration
- 3-4 common errors encountered
- 30-45 minutes total time
With this skill:
- ~3,500 tokens: Direct templates, error prevention
- 0 errors (all documented issues prevented)
- 10-15 minutes total time
Savings: ~55-65% tokens, ~70% time
Production Validation
Tested with:
- React 19.2
- Vite 7.3
- TypeScript 5.9
- Cloudflare Workers (Wrangler 4.0)
- TanStack Query v5.101.4 (tanstack-query skill)
- Tailwind v4 + shadcn/ui (tailwind-v4-shadcn skill)
Stack compatibility:
- ✅ Cloudflare Workers + Static Assets
- ✅ Cloudflare D1 database
- ✅ TanStack Query integration
- ✅ React 19.2+ server components
- ✅ TypeScript strict mode
- ✅ Vite 7.3+ build optimization
Further Reading
Last Updated: 2025-12-09
Skill Version: 1.1.0
Library Version: @tanstack/react-table v8.21.3