TanStack Table について
TanStack Table v8でヘッドレスなデータテーブルを構築するためのClaudeスキルです。サーバーサイド処理、Cloudflare連携、大規模データの性能最適化に重点を置いています。
主な機能
- 01クライアント・サーバー両用テンプレート
- 02ページネーション・フィルター・ソートのパターン
- 03Cloudflare D1とWorkersの例
- 04TanStack Queryとの状態連携
- 05大規模データ向け仮想化
- 06よくあるエラーの対処法
活用シーン
- 01API駆動のページネーション付きテーブル構築
- 02テーブルデータとCloudflare D1の連携
- 03大規模なクライアント側データの効率的な表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ovachiever/ droid-tings --skill tanstack-tableインストール後、Claude Code で/tanstack-tableを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonreferences/cloudflare-d1-examples.mdcommon-errors.mdserver-side-patterns.mdquery-integration.mdperformance-virtualization.mdtemplates/column-configuration.tsxbasic-client-table.tsxpackage.jsond1-database-example.tsxshadcn-styled-table.tsxserver-paginated-table.tsxvirtualized-large-dataset.tsx詳細
TanStack Table スキル
TanStack Table v8 を使用して、本番環境に対応したヘッドレスデータテーブルを構築します。サーバーサイドのパターンと Cloudflare Workers との統合に最適化されています。
このスキルを使用するタイミング
次のような語句を含む場合に自動的に起動します:
- 「データテーブル」または「データグリッド」
- 「サーバーサイドページネーション」または「サーバーサイドフィルタリング」
- 「TanStack Table」または「React Table」
- 「大規模データセットを含むテーブル」
- 「API でページネーション/フィルタリング/ソート」
- 「Cloudflare D1 テーブル統合」
- 「テーブルの仮想化」または「大規模リストのパフォーマンス」
次のような場合にこのスキルを使用します:
- ページネーション、フィルタリング、ソートを備えたデータテーブルを構築する
- サーバーサイドのテーブル機能(API 駆動)を実装する
- データ取得に TanStack Query を使ってテーブルを統合する
- 仮想化が必要な大規模データセット(1000 行以上)を扱う
- テーブルを Cloudflare D1 データベースに接続する
- 独自の UI に依存しないヘッドレスのテーブルロジックが必要である
- 他のテーブルライブラリから TanStack Table v8 に移行する
このスキルでできること
1. 本番環境向けテンプレート(6 種類)
- 基本的なクライアントサイドテーブル - ローカルデータを使ったシンプルなテーブル
- サーバーサイドページネーションテーブル - TanStack Query を使った API 駆動のページネーション
- D1 データベース統合 - Cloudflare D1 + Workers API + テーブル
- カラム設定パターン - 型安全なカラム定義
- 大規模データセットの仮想化 - TanStack Virtual によるパフォーマンス最適化
- shadcn/ui スタイルのテーブル - Tailwind v4 + shadcn との統合
2. サーバーサイドのパターン
- API バックエンドを使ったページネーション
- クエリパラメーターを使ったフィルタリング
- データベースクエリを使ったソート
- 状態管理(ページ、フィルター、ソート)
- URL との同期
- TanStack Query との連携
3. Cloudflare との統合
- D1 データベースのクエリパターン
- テーブルデータ用の Workers API エンドポイント
- SQL でのページネーション + フィルタリング + ソート
- バインディングの設定(wrangler.jsonc)
- クライアントサイド統合パターン
4. パフォーマンスの最適化
- TanStack Virtual による仮想化
- 大規模データセットのレンダリング(10k 行以上)
- メモリー効率の高いパターン
- useVirtualizer() の統合
5. エラーの防止
よくある 6 件以上の問題について説明し、防止します:
- サーバーサイドの状態管理に関する混乱
- TanStack Query の統合エラー(クエリーキーの連携)
- API バックエンドでのカラムフィルタリング
- 手動ソートの設定ミス
- URL 状態の同期に関する問題
- 大規模データセットのパフォーマンス問題
クイックスタート
インストール
npm install @tanstack/react-table@latest
# For virtualization (optional):
npm install @tanstack/react-virtual@latest
最新の検証済みバージョン:
@tanstack/react-table: v8.21.3@tanstack/react-virtual: v3.13.12
基本的なクライアントサイドテーブル
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>
)
}
サーバーサイドのパターン(大規模データセットに推奨)
パターン 1:TanStack Query を使ったサーバーサイドページネーション
Cloudflare Workers API エンドポイント:
// 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),
},
})
}
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>
)
}
パターン 2:サーバーサイドフィルタリング
フィルターに対応した API:
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 })
}
クライアントサイド:
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,
})
大規模データセットの仮想化
1000 行以上の場合は、TanStack Virtual を使って表示範囲内の行だけをレンダリングします:
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>
)
}
よくあるエラーと解決策
エラー 1:無限再レンダリング
問題: テーブルが無限に再レンダリングされ、ブラウザーがフリーズします。
原因: data または columns の参照がレンダリングのたびに変わります。
解決策: 必ず useMemo または 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() })
}
エラー 2:TanStack Query とテーブル状態の不一致
問題: クエリーが再取得されてもページネーション状態が同期されず、古いデータが表示されます。
解決策: すべてのテーブル状態をクエリーキーに含めます:
// ❌ 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())
}
})
エラー 3:サーバーサイド機能が動作しない
問題: ページネーション/フィルタリング/ソートを行っても API が呼び出されません。
解決策: manual* フラグを 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
})
エラー 4:カラムヘルパーで TypeScript の「Cannot Find Module」エラーが発生する
問題: createColumnHelper のインポートエラーが発生します。
解決策: 正しいパスからインポートします:
// ❌ 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!
}),
]
エラー 5:サーバーサイドでソートが動作しない
問題: ソート用ヘッダーをクリックしてもデータが更新されません。
解決策: ソートをクエリーキーと API 呼び出しに含めます:
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,
})
エラー 6:大規模データセットでのパフォーマンス低下
問題: 1000 行以上になると、テーブルの動作が遅くなったり、カクついたりします。
解決策: 仮想化を使用する(上記の例を参照)か、サーバーサイドページネーションを実装してください。
既存のスキルとの連携
tanstack-query スキルとの連携
TanStack Table + TanStack Query が推奨されるパターンです。
// 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(),
})
cloudflare-d1 スキルとの連携
// 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())
})
tailwind-v4-shadcn スキルとの連携
shadcn/ui の Table コンポーネントと TanStack Table のロジックを使用します。
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>
)
}
ベストプラクティス
1. データとカラムは必ずメモ化する
const data = useMemo(() => [...], [dependencies])
const columns = useMemo(() => [...], [])
2. 大規模データセットにはサーバーサイドを使用する
- クライアントサイド:1000 行未満
- サーバーサイド:1000 行以上、または頻繁に変化するデータ
3. クエリーキーとテーブルの状態を連携させる
queryKey: ['resource', pagination, filters, sorting]
4. ローディング状態を用意する
if (isLoading) return <TableSkeleton />
if (error) return <ErrorMessage error={error} />
5. 型安全性のために Column Helper を使用する
const columnHelper = createColumnHelper<YourType>()
const columns = [
columnHelper.accessor('field', { /* fully typed */ })
]
6. クライアントサイドの大規模テーブルを仮想化する
if (data.length > 1000) {
// Use TanStack Virtual (see example above)
}
テンプレートリファレンス
すべてのテンプレートは ~/.claude/skills/tanstack-table/templates/ にあります。
- package.json - 依存関係とバージョン
- basic-client-table.tsx - シンプルなクライアントサイドテーブル
- server-paginated-table.tsx - Query を使用したサーバーサイドページネーション
- d1-database-example.tsx - Cloudflare D1 との連携
- column-configuration.tsx - 型安全なカラムのパターン
- virtualized-large-dataset.tsx - Virtual によるパフォーマンス向上
- shadcn-styled-table.tsx - Tailwind v4 + shadcn UI のスタイリング
参考ドキュメント
詳細ガイドは ~/.claude/skills/tanstack-table/references/ にあります。
- server-side-patterns.md - API を使用したページネーション、フィルタリング、ソート
- query-integration.md - TanStack Query との連携
- cloudflare-d1-examples.md - Workers + D1 の完全な例
- performance-virtualization.md - TanStack Virtual ガイド
- common-errors.md - 解決策付きの、文書化された 6 件以上の問題
トークン効率
このスキルを使用しない場合:
- 約 8,000 トークン:v8 の変更点、サーバーサイドのパターン、Query との連携を調査
- よくあるエラーが 3~4 件発生
- 合計所要時間は 30~45 分
このスキルを使用する場合:
- 約 3,500 トークン:テンプレートを直接使用し、エラーを防止
- エラー 0 件(文書化された問題をすべて防止)
- 合計所要時間は 10~15 分
削減効果: トークンを約 55~65%、時間を約 70%削減
本番環境での検証
テスト環境:
- React 19.2
- Vite 6.0
- TypeScript 5.8
- Cloudflare Workers(Wrangler 4.0)
- TanStack Query v5.90.7(tanstack-query スキル)
- Tailwind v4 + shadcn/ui(tailwind-v4-shadcn スキル)
スタックの互換性:
- ✅ Cloudflare Workers + Static Assets
- ✅ Cloudflare D1 データベース
- ✅ TanStack Query との連携
- ✅ React 19.2 以降のサーバーコンポーネント
- ✅ TypeScript の strict モード
- ✅ Vite 6.0 以降のビルド最適化
さらに読む
- 公式ドキュメント: https://tanstack.com/table/latest
- TanStack Virtual: https://tanstack.com/virtual/latest
- GitHub: https://github.com/TanStack/table
- Cloudflare D1 スキル:
~/.claude/skills/cloudflare-d1/ - TanStack Query スキル:
~/.claude/skills/tanstack-query/
最終更新日: 2025-11-07 スキルのバージョン: 1.0.0 ライブラリのバージョン: @tanstack/react-table v8.21.3