TanStack Table
About
This skill provides server-side pagination, filtering, and sorting patterns for building production-ready data tables with TanStack Table v8. It's optimized for Cloudflare Workers and D1 databases and includes solutions for common integration errors. Use it when implementing server-side table features or handling large datasets requiring performance optimization.
Quick Install
Claude Code
Recommended/plugin add https://github.com/jezweb/claude-skillsgit clone https://github.com/jezweb/claude-skills.git ~/.claude/skills/TanStack TableCopy and paste this command in Claude Code to install this skill
Documentation
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 (6)
- 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
- 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. 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
Quick Start
Installation
npm install @tanstack/react-table@latest
# For virtualization (optional):
npm install @tanstack/react-virtual@latest
Latest verified versions:
@tanstack/react-table: v8.21.3@tanstack/react-virtual: v3.13.12
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() {
// CRITICAL: Memoize data and columns to prevent infinite re-renders
const data = useMemo<User[]>(() => [
{ id: '1', name: 'Alice', email: '[email protected]' },
{ id: '2', name: 'Bob', email: '[email protected]' },
], [])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(), // Required
})
return (
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : header.column.columnDef.header}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{cell.renderValue()}
</td>
))}
</tr>
))}
</tbody>
</table>
)
}
Server-Side Patterns (Recommended for Large Datasets)
Pattern 1: Server-Side Pagination with TanStack Query
Cloudflare Workers API Endpoint:
// src/routes/api/users.ts
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
// Query D1 database
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()
// Get total count for pagination
const countResult = await context.env.DB.prepare(`
SELECT COUNT(*) as total FROM users
`).first<{ total: number }>()
return Response.json({
data: results,
pagination: {
page,
pageSize,
total: countResult?.total || 0,
pageCount: Math.ceil((countResult?.total || 0) / 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,
})
// TanStack Query fetches data
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()
},
})
// TanStack Table manages display
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
// Server-side pagination config
manualPagination: true, // CRITICAL: Tell table pagination is manual
pageCount: data?.pagination.pageCount ?? 0,
state: { pagination },
onPaginationChange: setPagination,
})
if (isLoading) return <div>Loading...</div>
return (
<div>
<table>{/* render table */}</table>
{/* Pagination controls */}
<div>
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</button>
<span>
Page {table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</span>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</button>
</div>
</div>
)
}
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, // CRITICAL: Server handles filtering
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, // 10k+ rows
columns,
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()
// Virtualize rows
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 50, // Row height in px
overscan: 10, // Render 10 extra rows for smooth scrolling
})
return (
<div ref={tableContainerRef} style={{ height: '600px', overflow: 'auto' }}>
<table style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
<thead>{/* header */}</thead>
<tbody>
{rowVirtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
<tr
key={row.id}
style={{
position: 'absolute',
transform: `translateY(${virtualRow.start}px)`,
width: '100%',
}}
>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{cell.renderValue()}</td>
))}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
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:
// ❌ BAD: New array reference every render
function Table() {
const data = [{ id: 1 }] // Creates new array!
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
// ✅ GOOD: Stable reference
function Table() {
const data = useMemo(() => [{ id: 1 }], []) // Stable
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
// ✅ ALSO GOOD: Define outside component
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:
// ❌ BAD: Missing pagination in query key
const { data } = useQuery({
queryKey: ['users'], // Doesn't include page!
queryFn: () => fetch(`/api/users?page=${pagination.pageIndex}`).then(r => r.json())
})
// ✅ GOOD: Complete query key
const { data } = useQuery({
queryKey: ['users', pagination.pageIndex, pagination.pageSize, columnFilters, sorting],
queryFn: () => {
const params = new URLSearchParams({
page: pagination.pageIndex.toString(),
pageSize: pagination.pageSize.toString(),
// ... filters, sorting
})
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(),
// CRITICAL: Tell table these are server-side
manualPagination: true,
manualFiltering: true,
manualSorting: true,
pageCount: serverPageCount, // Must provide total page count
})
Error 4: TypeScript "Cannot Find Module" for Column Helper
Problem: Import errors for createColumnHelper.
Solution: Import from correct path:
// ❌ BAD: Wrong path
import { createColumnHelper } from '@tanstack/table-core'
// ✅ GOOD: Correct path
import { createColumnHelper } from '@tanstack/react-table'
// Usage for type-safe columns
const columnHelper = createColumnHelper<User>()
const columns = [
columnHelper.accessor('name', {
header: 'Name',
cell: info => info.getValue(), // Fully typed!
}),
]
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], // Include 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:
// Query handles data fetching + caching
const { data, isLoading } = useQuery({
queryKey: ['users', tableState],
queryFn: fetchUsers,
})
// Table handles display + interactions
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
})
With cloudflare-d1 Skill
// Cloudflare Workers API (from cloudflare-d1 skill patterns)
export async function onRequestGet({ env }: { env: Env }) {
const { results } = await env.DB.prepare('SELECT * FROM users LIMIT 20').all()
return Response.json({ data: results })
}
// Client-side table consumes D1 data
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({ /* config */ })
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>
</Table>
)
}
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', { /* fully typed */ })
]
6. Virtualize Large Client-Side Tables
if (data.length > 1000) {
// Use TanStack Virtual (see example above)
}
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
- 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
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 6.0
- TypeScript 5.8
- Cloudflare Workers (Wrangler 4.0)
- TanStack Query v5.90.7 (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 6.0+ build optimization
Further Reading
- Official Docs: https://tanstack.com/table/latest
- TanStack Virtual: https://tanstack.com/virtual/latest
- GitHub: https://github.com/TanStack/table
- Cloudflare D1 Skill:
~/.claude/skills/cloudflare-d1/ - TanStack Query Skill:
~/.claude/skills/tanstack-query/
Last Updated: 2025-11-07 Skill Version: 1.0.0 Library Version: @tanstack/react-table v8.21.3
GitHub Repository
Related Skills
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
Algorithmic Art Generation
MetaThis skill helps developers create algorithmic art using p5.js, focusing on generative art, computational aesthetics, and interactive visualizations. It automatically activates for topics like "generative art" or "p5.js visualization" and guides you through creating unique algorithms with features like seeded randomness, flow fields, and particle systems. Use it when you need to build reproducible, code-driven artistic patterns.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
requesting-code-review
DesignThis skill dispatches a code-reviewer subagent to analyze code changes against requirements before proceeding. It should be used after completing tasks, implementing major features, or before merging to main. The review helps catch issues early by comparing the current implementation with the original plan.
