tanstack-table by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill tanstack-table适用于 Cloudflare Workers + D1 的无头数据表格,支持服务器端分页、筛选、排序和虚拟化
最后更新 : 2026-01-09 版本 : @tanstack/react-table@8.21.3, @tanstack/react-virtual@3.13.18
npm install @tanstack/react-table@latest
npm install @tanstack/react-virtual@latest # 用于虚拟化
基本设置 (关键:使用 memoize 处理数据/列以防止无限重新渲染):
import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
const columns: ColumnDef<User>[] = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
]
function UsersTable() {
const data = useMemo(() => [...users], []) // 稳定的引用
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
return (
<table>
<thead>
{table.getHeaderGroups().map(group => (
<tr key={group.id}>
{group.headers.map(h => <th key={h.id}>{h.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>
)
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Cloudflare D1 API (分页 + 筛选 + 排序):
// Workers API: functions/api/users.ts
export async function onRequestGet({ request, env }) {
const url = new URL(request.url)
const page = Number(url.searchParams.get('page')) || 0
const pageSize = 20
const search = url.searchParams.get('search') || ''
const sortBy = url.searchParams.get('sortBy') || 'created_at'
const sortOrder = url.searchParams.get('sortOrder') || 'DESC'
const { results } = await env.DB.prepare(`
SELECT * FROM users
WHERE name LIKE ? OR email LIKE ?
ORDER BY ${sortBy} ${sortOrder}
LIMIT ? OFFSET ?
`).bind(`%${search}%`, `%${search}%`, pageSize, page * pageSize).all()
const { total } = await env.DB.prepare('SELECT COUNT(*) as total FROM users').first()
return Response.json({
data: results,
pagination: { page, pageSize, total, pageCount: Math.ceil(total / pageSize) },
})
}
客户端 (TanStack Query + Table):
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 })
const [columnFilters, setColumnFilters] = useState([])
const [sorting, setSorting] = useState([])
// 关键:将所有状态包含在查询键中
const { data, isLoading } = useQuery({
queryKey: ['users', pagination, columnFilters, sorting],
queryFn: async () => {
const params = new URLSearchParams({
page: pagination.pageIndex,
search: columnFilters.find(f => f.id === 'search')?.value || '',
sortBy: sorting[0]?.id || 'created_at',
sortOrder: sorting[0]?.desc ? 'DESC' : 'ASC',
})
return fetch(`/api/users?${params}`).then(r => r.json())
},
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
// 关键:manual* 标志告诉表格由服务器处理这些功能
manualPagination: true,
manualFiltering: true,
manualSorting: true,
pageCount: data?.pagination.pageCount ?? 0,
state: { pagination, columnFilters, sorting },
onPaginationChange: setPagination,
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
})
仅渲染可见行以提升性能:
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualizedTable() {
const containerRef = useRef<HTMLDivElement>(null)
const table = useReactTable({ data: largeDataset, columns, getCoreRowModel: getCoreRowModel() })
const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 50, // 行高(像素)
overscan: 10,
})
return (
<div ref={containerRef} style={{ height: '600px', overflow: 'auto' }}>
<table style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
<tbody>
{rowVirtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
<tr key={row.id} style={{ position: 'absolute', transform: `translateY(${virtualRow.start}px)` }}>
{row.getVisibleCells().map(cell => <td key={cell.id}>{cell.renderValue()}</td>)}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
已知问题 : 在标签页内容或模态框内部使用虚拟化时,如果这些容器使用 display: none 隐藏非活动内容,虚拟化器会继续在隐藏状态下执行布局计算,导致:
来源 : GitHub Issue #6109
预防措施 :
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 50,
overscan: 10,
// 当容器隐藏时禁用,以防止无限重新渲染
enabled: containerRef.current?.getClientRects().length !== 0,
})
// 或者:有条件地渲染,而不是使用 CSS 隐藏
{isVisible && <VirtualizedTable />}
固定列或行,使其在水平/垂直滚动时保持可见:
import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
// 启用固定功能
enableColumnPinning: true,
enableRowPinning: true,
// 初始固定状态
initialState: {
columnPinning: {
left: ['select', 'name'], // 固定在左侧
right: ['actions'], // 固定在右侧
},
},
})
// 渲染带固定列的表格
function PinnedTable() {
return (
<div className="flex">
{/* 左侧固定列 */}
<div className="sticky left-0 bg-background z-10">
{table.getLeftHeaderGroups().map(/* 渲染左侧表头 */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getLeftVisibleCells().map(/* 渲染单元格 */)}</tr>
))}
</div>
{/* 中间可滚动列 */}
<div className="overflow-x-auto">
{table.getCenterHeaderGroups().map(/* 渲染中间表头 */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getCenterVisibleCells().map(/* 渲染单元格 */)}</tr>
))}
</div>
{/* 右侧固定列 */}
<div className="sticky right-0 bg-background z-10">
{table.getRightHeaderGroups().map(/* 渲染右侧表头 */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getRightVisibleCells().map(/* 渲染单元格 */)}</tr>
))}
</div>
</div>
)
}
// 以编程方式切换固定状态
column.pin('left') // 将列固定在左侧
column.pin('right') // 将列固定在右侧
column.pin(false) // 取消固定列
row.pin('top') // 将行固定在顶部
row.pin('bottom') // 将行固定在底部
已知问题 : 固定父级分组列(使用 columnHelper.group() 创建)会导致定位错误和重复的表头。column.getStart('left') 为分组表头返回错误的值。
来源 : GitHub Issue #5397
预防措施 :
// 禁用分组列的固定功能
const isPinnable = (column) => !column.parent
// 或者:固定组内的单个列,而不是组本身
table.getColumn('firstName')?.pin('left')
table.getColumn('lastName')?.pin('left')
// 不要固定父级分组列
显示/隐藏子行或附加详情:
import { useReactTable, getCoreRowModel, getExpandedRowModel } from '@tanstack/react-table'
// 包含嵌套子项的数据
const data = [
{
id: 1,
name: '父行',
subRows: [
{ id: 2, name: '子行 1' },
{ id: 3, name: '子行 2' },
],
},
]
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(), // 展开功能必需
getSubRows: row => row.subRows, // 告诉表格子项的位置
})
// 渲染带展开按钮的表格
function ExpandableTable() {
return (
<tbody>
{table.getRowModel().rows.map(row => (
<>
<tr key={row.id}>
<td>
{row.getCanExpand() && (
<button onClick={row.getToggleExpandedHandler()}>
{row.getIsExpanded() ? '▼' : '▶'}
</button>
)}
</td>
{row.getVisibleCells().map(cell => (
<td key={cell.id} style={{ paddingLeft: `${row.depth * 20}px` }}>
{cell.renderValue()}
</td>
))}
</tr>
</>
))}
</tbody>
)
}
// 以编程方式控制展开
table.toggleAllRowsExpanded() // 展开/折叠所有行
row.toggleExpanded() // 切换单行展开状态
table.getIsAllRowsExpanded() // 检查是否全部展开
详情行 (自定义内容,非嵌套数据):
function DetailRow({ row }) {
if (!row.getIsExpanded()) return null
return (
<tr>
<td colSpan={columns.length}>
<div className="p-4 bg-muted">
行 {row.id} 的自定义详情内容
</div>
</td>
</tr>
)
}
按列值对行进行分组:
import { useReactTable, getCoreRowModel, getGroupedRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getGroupedRowModel: getGroupedRowModel(), // 分组功能必需
getExpandedRowModel: getExpandedRowModel(), // 分组可展开
initialState: {
grouping: ['status'], // 按 'status' 列分组
},
})
// 带聚合的列
const columns = [
{
accessorKey: 'status',
header: '状态',
},
{
accessorKey: 'amount',
header: '金额',
aggregationFn: 'sum', // 对分组值求和
aggregatedCell: ({ getValue }) => `总计: ${getValue()}`,
},
]
// 渲染分组表格
function GroupedTable() {
return (
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{cell.getIsGrouped() ? (
// 分组单元格 - 显示带展开切换的分组表头
<button onClick={row.getToggleExpandedHandler()}>
{row.getIsExpanded() ? '▼' : '▶'} {cell.renderValue()} ({row.subRows.length})
</button>
) : cell.getIsAggregated() ? (
// 聚合单元格 - 显示聚合结果
cell.renderValue()
) : cell.getIsPlaceholder() ? null : (
// 常规单元格
cell.renderValue()
)}
</td>
))}
</tr>
))}
</tbody>
)
}
// 内置聚合函数
// 'sum', 'min', 'max', 'extent', 'mean', 'median', 'unique', 'uniqueCount', 'count'
已知问题 : 分组功能在中等至大型数据集上会导致显著的性能下降。启用分组后,由于 createRow 计算中过多的内存使用,在 5 万行数据上的渲染时间可能从 <1 秒增加到 30-40 秒。
已验证 : 社区测试 + GitHub 问题报告
预防措施 :
// 1. 对大型数据集使用服务器端分组
// 2. 实现分页以限制每页行数
// 3. 对 10k+ 行禁用分组
const shouldEnableGrouping = data.length < 10000
// 4. 或者:在行组件上使用 React.memo
const MemoizedRow = React.memo(TableRow)
问题 #1: 无限重新渲染
data 或 columns 引用在每次渲染时都发生变化useMemo(() => [...], []) 或在组件外部定义数据/列问题 #2: Query + Table 状态不匹配
queryKey: ['users', pagination, columnFilters, sorting]问题 #3: 服务器端功能不工作
manual* 标志manualPagination: true, manualFiltering: true, manualSorting: true + 提供 pageCount问题 #4: TypeScript "无法找到模块"
createColumnHelper 的导入错误@tanstack/react-table 导入 (而不是 @tanstack/table-core)问题 #5: 服务器端排序不工作
sorting 包含在查询键中,向 API 调用添加排序参数,设置 manualSorting: true + onSortingChange问题 #6: 性能差 (1000+ 行)
问题 #7: React 编译器不兼容 (React 19+)
错误 : "数据更改时表格不重新渲染" (启用 React Compiler 时)
来源 : GitHub Issue #5567
发生原因 : React Compiler 的自动记忆化与表格核心实例冲突,当数据/状态更改时阻止重新渲染
预防措施 : 在使用 useReactTable 的组件顶部添加 "use no memo" 指令:
"use no memo"
function TableComponent() { const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() }) // 现在可以与 React Compiler 正确配合工作 }
注意 : 此问题也影响列可见性和行选择。完整修复将在 v9 版本中提供。
问题 #8: 服务器端分页行选择错误
错误 : toggleAllRowsSelected(false) 仅取消选择当前页,而不是所有页
来源 : GitHub Issue #5929
发生原因 : 选择状态跨页持久化(对于服务器端用例是故意的),但表头复选框状态计算不正确
预防措施 : 切换关闭时手动清除选择状态:
const toggleAllRows = (value: boolean) => { if (!value) { table.setRowSelection({}) // 清除整个选择对象 } else { table.toggleAllRowsSelected(true) } }
问题 #9: 客户端 onPaginationChange 返回错误的 pageIndex
错误 : onPaginationChange 总是返回 pageIndex: 0 而不是当前页
来源 : GitHub Issue #5970
发生原因 : 客户端分页模式存在状态跟踪错误(仅发生在客户端模式,服务器/手动模式工作正常)
预防措施 : 切换到手动分页以获得正确行为:
// 而不是依赖客户端分页 const table = useReactTable({ data, columns, manualPagination: true, // 强制正确的状态跟踪 pageCount: Math.ceil(data.length / pagination.pageSize), state: { pagination }, onPaginationChange: setPagination, })
问题 #10: 数据移除时行选择未清理
错误 : 已选择但不再存在于数据中的行仍保留在选择状态中
来源 : GitHub Issue #5850
发生原因 : 故意行为以支持服务器端分页(行从当前页消失但应保持选中状态)
预防措施 : 移除数据时手动清理选择:
const removeRow = (idToRemove: string) => { // 从数据中移除 setData(data.filter(row => row.id !== idToRemove))
// 如果它被选中,则清理选择 const { rowSelection } = table.getState() if (rowSelection[idToRemove]) { table.setRowSelection((old) => { const filtered = Object.entries(old).filter(([id]) => id !== idToRemove) return Object.fromEntries(filtered) }) } }
// 或者:使用 table.resetRowSelection(true) 清除所有选择
问题 #11: 打开 React DevTools 时性能下降
问题 #12: TypeScript getValue() 在分组列中的类型推断
错误 : 在 columnHelper.group() 内部,getValue() 返回 unknown 而不是访问器的实际类型
来源 : GitHub Issue #5860
修复 : 手动指定类型或使用 renderValue():
// 选项 1: 类型断言 cell: (info) => { const value = info.getValue() as string return value.toUpperCase() }
// 选项 2: 使用 renderValue() (更好的类型推断) cell: (info) => { const value = info.renderValue() return typeof value === 'string' ? value.toUpperCase() : value }
相关技能 : tanstack-query (数据获取), cloudflare-d1 (数据库后端), tailwind-v4-shadcn (UI 样式)
最后验证 : 2026-01-21 | 技能版本 : 2.0.0 | 变更 : 根据 TIER 1-2 研究发现添加了 7 个新的已知问题(React 19 Compiler、服务器端行选择、隐藏容器中的虚拟化、客户端分页错误、分组列固定、行选择清理、DevTools 性能、TypeScript getValue)。错误数量: 6 → 12。
每周安装数
493
仓库
GitHub 星标数
643
首次出现
Jan 31, 2026
安全审计
安装于
opencode337
gemini-cli315
codex313
claude-code305
github-copilot286
cursor273
Headless data tables with server-side pagination, filtering, sorting, and virtualization for Cloudflare Workers + D1
Last Updated : 2026-01-09 Versions : @tanstack/react-table@8.21.3, @tanstack/react-virtual@3.13.18
npm install @tanstack/react-table@latest
npm install @tanstack/react-virtual@latest # For virtualization
Basic Setup (CRITICAL: memoize data/columns to prevent infinite re-renders):
import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
const columns: ColumnDef<User>[] = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
]
function UsersTable() {
const data = useMemo(() => [...users], []) // Stable reference
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
return (
<table>
<thead>
{table.getHeaderGroups().map(group => (
<tr key={group.id}>
{group.headers.map(h => <th key={h.id}>{h.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>
)
}
Cloudflare D1 API (pagination + filtering + sorting):
// Workers API: functions/api/users.ts
export async function onRequestGet({ request, env }) {
const url = new URL(request.url)
const page = Number(url.searchParams.get('page')) || 0
const pageSize = 20
const search = url.searchParams.get('search') || ''
const sortBy = url.searchParams.get('sortBy') || 'created_at'
const sortOrder = url.searchParams.get('sortOrder') || 'DESC'
const { results } = await env.DB.prepare(`
SELECT * FROM users
WHERE name LIKE ? OR email LIKE ?
ORDER BY ${sortBy} ${sortOrder}
LIMIT ? OFFSET ?
`).bind(`%${search}%`, `%${search}%`, pageSize, page * pageSize).all()
const { total } = await env.DB.prepare('SELECT COUNT(*) as total FROM users').first()
return Response.json({
data: results,
pagination: { page, pageSize, total, pageCount: Math.ceil(total / pageSize) },
})
}
Client-Side (TanStack Query + Table):
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 })
const [columnFilters, setColumnFilters] = useState([])
const [sorting, setSorting] = useState([])
// CRITICAL: Include ALL state in query key
const { data, isLoading } = useQuery({
queryKey: ['users', pagination, columnFilters, sorting],
queryFn: async () => {
const params = new URLSearchParams({
page: pagination.pageIndex,
search: columnFilters.find(f => f.id === 'search')?.value || '',
sortBy: sorting[0]?.id || 'created_at',
sortOrder: sorting[0]?.desc ? 'DESC' : 'ASC',
})
return fetch(`/api/users?${params}`).then(r => r.json())
},
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
// CRITICAL: manual* flags tell table server handles these
manualPagination: true,
manualFiltering: true,
manualSorting: true,
pageCount: data?.pagination.pageCount ?? 0,
state: { pagination, columnFilters, sorting },
onPaginationChange: setPagination,
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
})
Render only visible rows for performance:
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualizedTable() {
const containerRef = useRef<HTMLDivElement>(null)
const table = useReactTable({ data: largeDataset, columns, getCoreRowModel: getCoreRowModel() })
const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 50, // Row height px
overscan: 10,
})
return (
<div ref={containerRef} style={{ height: '600px', overflow: 'auto' }}>
<table style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
<tbody>
{rowVirtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
<tr key={row.id} style={{ position: 'absolute', transform: `translateY(${virtualRow.start}px)` }}>
{row.getVisibleCells().map(cell => <td key={cell.id}>{cell.renderValue()}</td>)}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
Known Issue : When using virtualization inside tabbed content or modals that hide inactive content with display: none, the virtualizer continues performing layout calculations while hidden, causing:
Source : GitHub Issue #6109
Prevention :
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => containerRef.current,
estimateSize: () => 50,
overscan: 10,
// Disable when container is hidden to prevent infinite re-renders
enabled: containerRef.current?.getClientRects().length !== 0,
})
// OR: Conditionally render instead of hiding with CSS
{isVisible && <VirtualizedTable />}
Pin columns or rows to keep them visible during horizontal/vertical scroll:
import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
// Enable pinning
enableColumnPinning: true,
enableRowPinning: true,
// Initial pinning state
initialState: {
columnPinning: {
left: ['select', 'name'], // Pin to left
right: ['actions'], // Pin to right
},
},
})
// Render with pinned columns
function PinnedTable() {
return (
<div className="flex">
{/* Left pinned columns */}
<div className="sticky left-0 bg-background z-10">
{table.getLeftHeaderGroups().map(/* render left headers */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getLeftVisibleCells().map(/* render cells */)}</tr>
))}
</div>
{/* Center scrollable columns */}
<div className="overflow-x-auto">
{table.getCenterHeaderGroups().map(/* render center headers */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getCenterVisibleCells().map(/* render cells */)}</tr>
))}
</div>
{/* Right pinned columns */}
<div className="sticky right-0 bg-background z-10">
{table.getRightHeaderGroups().map(/* render right headers */)}
{table.getRowModel().rows.map(row => (
<tr>{row.getRightVisibleCells().map(/* render cells */)}</tr>
))}
</div>
</div>
)
}
// Toggle pinning programmatically
column.pin('left') // Pin column to left
column.pin('right') // Pin column to right
column.pin(false) // Unpin column
row.pin('top') // Pin row to top
row.pin('bottom') // Pin row to bottom
Known Issue : Pinning parent group columns (created with columnHelper.group()) causes incorrect positioning and duplicated headers. column.getStart('left') returns wrong values for group headers.
Source : GitHub Issue #5397
Prevention :
// Disable pinning for grouped columns
const isPinnable = (column) => !column.parent
// OR: Pin individual columns within group, not the group itself
table.getColumn('firstName')?.pin('left')
table.getColumn('lastName')?.pin('left')
// Don't pin the parent group column
Show/hide child rows or additional details:
import { useReactTable, getCoreRowModel, getExpandedRowModel } from '@tanstack/react-table'
// Data with nested children
const data = [
{
id: 1,
name: 'Parent Row',
subRows: [
{ id: 2, name: 'Child Row 1' },
{ id: 3, name: 'Child Row 2' },
],
},
]
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(), // Required for expanding
getSubRows: row => row.subRows, // Tell table where children are
})
// Render with expand button
function ExpandableTable() {
return (
<tbody>
{table.getRowModel().rows.map(row => (
<>
<tr key={row.id}>
<td>
{row.getCanExpand() && (
<button onClick={row.getToggleExpandedHandler()}>
{row.getIsExpanded() ? '▼' : '▶'}
</button>
)}
</td>
{row.getVisibleCells().map(cell => (
<td key={cell.id} style={{ paddingLeft: `${row.depth * 20}px` }}>
{cell.renderValue()}
</td>
))}
</tr>
</>
))}
</tbody>
)
}
// Control expansion programmatically
table.toggleAllRowsExpanded() // Expand/collapse all
row.toggleExpanded() // Toggle single row
table.getIsAllRowsExpanded() // Check if all expanded
Detail Rows (custom content, not nested data):
function DetailRow({ row }) {
if (!row.getIsExpanded()) return null
return (
<tr>
<td colSpan={columns.length}>
<div className="p-4 bg-muted">
Custom detail content for row {row.id}
</div>
</td>
</tr>
)
}
Group rows by column values:
import { useReactTable, getCoreRowModel, getGroupedRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getGroupedRowModel: getGroupedRowModel(), // Required for grouping
getExpandedRowModel: getExpandedRowModel(), // Groups are expandable
initialState: {
grouping: ['status'], // Group by 'status' column
},
})
// Column with aggregation
const columns = [
{
accessorKey: 'status',
header: 'Status',
},
{
accessorKey: 'amount',
header: 'Amount',
aggregationFn: 'sum', // Sum grouped values
aggregatedCell: ({ getValue }) => `Total: ${getValue()}`,
},
]
// Render grouped table
function GroupedTable() {
return (
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{cell.getIsGrouped() ? (
// Grouped cell - show group header with expand toggle
<button onClick={row.getToggleExpandedHandler()}>
{row.getIsExpanded() ? '▼' : '▶'} {cell.renderValue()} ({row.subRows.length})
</button>
) : cell.getIsAggregated() ? (
// Aggregated cell - show aggregation result
cell.renderValue()
) : cell.getIsPlaceholder() ? null : (
// Regular cell
cell.renderValue()
)}
</td>
))}
</tr>
))}
</tbody>
)
}
// Built-in aggregation functions
// 'sum', 'min', 'max', 'extent', 'mean', 'median', 'unique', 'uniqueCount', 'count'
Known Issue : The grouping feature causes significant performance degradation on medium-to-large datasets. With grouping enabled, render times can increase from <1 second to 30-40 seconds on 50k rows due to excessive memory usage in createRow calculations.
Source : Blog Post (JP Camara) | GitHub Issue #5926
Verified : Community testing + GitHub issue report
Prevention :
// 1. Use server-side grouping for large datasets
// 2. Implement pagination to limit rows per page
// 3. Disable grouping for 10k+ rows
const shouldEnableGrouping = data.length < 10000
// 4. OR: Use React.memo on row components
const MemoizedRow = React.memo(TableRow)
Issue #1: Infinite Re-Renders
data or columns references change on every renderuseMemo(() => [...], []) or define data/columns outside componentIssue #2: Query + Table State Mismatch
queryKey: ['users', pagination, columnFilters, sorting]Issue #3: Server-Side Features Not Working
manual* flagsmanualPagination: true, manualFiltering: true, manualSorting: true + provide pageCountIssue #4: TypeScript "Cannot Find Module"
createColumnHelper@tanstack/react-table (NOT @tanstack/table-core)Issue #5: Sorting Not Working Server-Side
sorting in query key, add sort params to API call, set manualSorting: true + onSortingChangeIssue #6: Poor Performance (1000+ Rows)
Issue #7: React Compiler Incompatibility (React 19+)
Error : "Table doesn't re-render when data changes" (with React Compiler enabled)
Source : GitHub Issue #5567
Why It Happens : React Compiler's automatic memoization conflicts with table core instance, preventing re-renders when data/state changes
Prevention : Add "use no memo" directive at top of components using useReactTable:
"use no memo"
function TableComponent() { const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() }) // Now works correctly with React Compiler }
Note : This issue also affects column visibility and row selection. Full fix coming in v9.
Issue #8: Server-Side Pagination Row Selection Bug
Error : toggleAllRowsSelected(false) only deselects current page, not all pages
Source : GitHub Issue #5929
Why It Happens : Selection state persists across pages (intentional for server-side use cases), but header checkbox state is calculated incorrectly
Prevention : Manually clear selection state when toggling off:
const toggleAllRows = (value: boolean) => { if (!value) { table.setRowSelection({}) // Clear entire selection object } else { table.toggleAllRowsSelected(true) } }
Issue #9: Client-Side onPaginationChange Returns Incorrect pageIndex
Error : onPaginationChange always returns pageIndex: 0 instead of current page
Source : GitHub Issue #5970
Why It Happens : Client-side pagination mode has state tracking bug (only occurs in client mode, works correctly in server/manual mode)
Prevention : Switch to manual pagination for correct behavior:
// Instead of relying on client-side pagination const table = useReactTable({ data, columns, manualPagination: true, // Forces correct state tracking pageCount: Math.ceil(data.length / pagination.pageSize), state: { pagination }, onPaginationChange: setPagination, })
Issue #10: Row Selection Not Cleaned Up When Data Removed
Error : Selected rows that no longer exist in data remain in selection state
Source : GitHub Issue #5850
Why It Happens : Intentional behavior to support server-side pagination (where rows disappear from current page but should stay selected)
Prevention : Manually clean up selection when removing data:
const removeRow = (idToRemove: string) => { // Remove from data setData(data.filter(row => row.id !== idToRemove))
// Clean up selection if it was selected const { rowSelection } = table.getState() if (rowSelection[idToRemove]) { table.setRowSelection((old) => { const filtered = Object.entries(old).filter(([id]) => id !== idToRemove) return Object.fromEntries(filtered) }) } }
// OR: Use table.resetRowSelection(true) to clear all
Issue #11: Performance Degradation with React DevTools Open
Issue #12: TypeScript getValue() Type Inference with Grouped Columns
Error : getValue() returns unknown instead of accessor's actual type inside columnHelper.group()
Source : GitHub Issue #5860
Fix : Manually specify type or use renderValue():
// Option 1: Type assertion cell: (info) => { const value = info.getValue() as string return value.toUpperCase() }
// Option 2: Use renderValue() (better type inference) cell: (info) => { const value = info.renderValue() return typeof value === 'string' ? value.toUpperCase() : value }
Related Skills : tanstack-query (data fetching), cloudflare-d1 (database backend), tailwind-v4-shadcn (UI styling)
Last verified : 2026-01-21 | Skill version : 2.0.0 | Changes : Added 7 new known issues from TIER 1-2 research findings (React 19 Compiler, server-side row selection, virtualization in hidden containers, client-side pagination bug, column pinning with groups, row selection cleanup, DevTools performance, TypeScript getValue). Error count: 6 → 12.
Weekly Installs
493
Repository
GitHub Stars
643
First Seen
Jan 31, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode337
gemini-cli315
codex313
claude-code305
github-copilot286
cursor273
Next.js 最佳实践指南:文件约定、RSC边界、异步模式与性能优化
46,400 周安装
OpenAI API 完整指南:GPT-5、GPT-4o、DALL-E 3、Whisper 集成与Node.js/JavaScript开发
465 周安装
客户旅程地图制作指南:5步创建跨职能客户体验地图,提升转化与忠诚度
466 周安装
Next.js 15+ 最佳实践指南:文件约定、RSC边界、异步模式与性能优化
466 周安装
Airflow 2 到 3 迁移指南:代码变更、元数据访问与自动化升级
467 周安装
市场状态识别与交易策略推荐 - 基于价格行为与波动率的AI交易技能
467 周安装
Google Gemini API 完整指南:最新SDK迁移、模型对比与实战教程(2025版)
467 周安装