mcpField
T
tanstack-table

tanstack-table

github.com/secondsky開発240504 回インストール5 週間前に更新
リポジトリ

tanstack-table について

TanStack Table v8でヘッドレスデータテーブルを構築する開発者向けのガイドとテンプレートです。サーバー側データ処理、Cloudflare Workers連携、大規模データセットの性能対策を扱います。

主な機能

  1. 01データテーブル用テンプレート7種
  2. 02サーバー側ページネーション・フィルタリング・ソート
  3. 03Cloudflare D1とWorkersのパターン
  4. 04TanStack Virtualによる仮想化
  5. 05列状態とフィルターの制御
  6. 06よくある問題のトラブルシューティング

活用シーン

  1. 01API連携のページ分割・フィルター付きテーブルの実装
  2. 02テーブルとCloudflare D1の接続
  3. 03大規模なクライアント側テーブルの仮想化
  4. 04別のテーブルライブラリからTanStack Table v8への移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/secondsky/claude-skills --skill tanstack-table

インストール後、Claude Code で/tanstack-tableを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
cloudflare-d1-examples.md
common-errors.md
feature-controls.md
performance-virtualization.md
query-integration.md
server-side-patterns.md
templates/
basic-client-table.tsx
column-configuration.tsx
controlled-table-state.tsx
d1-database-example.tsx
package.json
server-paginated-table.tsx
shadcn-styled-table.tsx
virtualized-large-dataset.tsx

詳細

TanStack Table スキル

サーバーサイドのパターンと Cloudflare Workers との連携に最適化された、TanStack Table v8 による本番環境対応のヘッドレスデータテーブルを構築します。


このスキルを使う場面

次の語句に言及すると自動的に起動します:

  • 「データテーブル」または「データグリッド」
  • 「サーバーサイドページネーション」または「サーバーサイドフィルタリング」
  • 「TanStack Table」または「React Table」
  • 「大規模データセットを含むテーブル」
  • 「API でページネーション/フィルタリング/ソートする」
  • 「Cloudflare D1 テーブルとの連携」
  • 「テーブルを仮想化する」または「大規模リストのパフォーマンス」

次の場合にこのスキルを使ってください:

  • ページネーション、フィルタリング、ソート機能を備えたデータテーブルを構築する
  • サーバーサイドのテーブル機能(API 駆動)を実装する
  • データ取得のためにテーブルを TanStack Query と連携する
  • 仮想化が必要な大規模データセット(1000 行以上)を扱う
  • テーブルを Cloudflare D1 データベースに接続する
  • 特定の UI に依存しないヘッドレスなテーブルロジックが必要
  • 他のテーブルライブラリから TanStack Table v8 に移行する

このスキルでできること

1. 本番環境向けテンプレート(7 種類)
  • 基本的なクライアントサイドテーブル - ローカルデータを使うシンプルなテーブル
  • サーバーサイドページネーション対応テーブル - 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 による仮想化
  • 大規模データセットのレンダリング(1 万行以上)
  • メモリー効率の高いパターン
  • useVirtualizer() との連携
5. 機能制御と UX
  • カラムの表示/非表示切り替えと固定(列の固定表示)
  • カラムの並び順とサイズのデフォルト設定
  • 全体検索、あいまい検索、ファセットフィルター
  • 行選択と行固定のパターン
  • パフォーマンス低下を防ぐための状態制御チェックリスト
6. エラーの防止

よくある問題を 6 件以上記載し、防止します:

  1. サーバーサイドの状態管理の混乱
  2. TanStack Query との連携エラー(クエリーキーの連携)
  3. API バックエンドでのカラムフィルタリング
  4. 手動ソートの設定ミス
  5. URL 状態の同期に関する問題
  6. 大規模データセットのパフォーマンス問題
  7. テーブル状態(columnSizingInfo)を過剰に制御したことによる余分な再レンダリング

クイックスタート

インストール
# Core table library
bun add @tanstack/react-table@latest

# Optional: For virtualization (1000+ rows)
bun add @tanstack/react-virtual@latest

# Optional: For fuzzy/global search
bun add @tanstack/match-sorter-utils@latest

最新の検証済みバージョン(2025-12-09 時点):

  • @tanstack/react-table:v8.21.3(安定版)
  • @tanstack/react-virtual:v3.13.23
  • @tanstack/match-sorter-utils:v8.21.3(あいまい検索用)

React のサポート: React 16.8 から React 19 まで動作します。React Compiler はサポートされていません。

基本的なクライアントサイドテーブル
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 で Column Helper の「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)
}
7. 必要な状態だけを制御する
  • 永続化または同期が必要な場合は、sorting、pagination、filters、visibility、pinning、order、selection を制御対象の状態にします。
  • ドラッグ状態を永続化する場合を除き、columnSizingInfo の制御は避けてください。頻繁に更新が発生し、パフォーマンスが低下する可能性があります。

テンプレートの一覧

すべてのテンプレートは ~/.claude/skills/tanstack-table/templates/ にあります。

  1. package.json - 依存関係とバージョン
  2. basic-client-table.tsx - シンプルなクライアントサイドテーブル
  3. server-paginated-table.tsx - Query を使用したサーバーサイドのページネーション
  4. d1-database-example.tsx - Cloudflare D1 との統合
  5. column-configuration.tsx - 型安全な列のパターン
  6. controlled-table-state.tsx - 表示、固定、順序、あいまい検索/グローバルフィルタリング、選択
  7. virtualized-large-dataset.tsx - Virtual を使用したパフォーマンス最適化
  8. shadcn-styled-table.tsx - Tailwind v4 と shadcn UI によるスタイリング

リファレンスドキュメント

詳細ガイドは ~/.claude/skills/tanstack-table/references/ にあります。

  1. server-side-patterns.md - API を使用したページネーション、フィルタリング、並べ替え
  2. query-integration.md - TanStack Query との連携
  3. cloudflare-d1-examples.md - Workers と D1 の完全な例
  4. performance-virtualization.md - TanStack Virtual ガイド
  5. common-errors.md - 解決策を含む、文書化された 6 件以上の問題
  6. feature-controls.md - 制御状態、表示、固定、順序、あいまい検索/グローバルフィルタリング、選択

リファレンスを読み込むタイミング

ユーザーのニーズに応じて、Claude は以下のリファレンスファイルの読み込みを提案してください。

references/common-errors.md を読み込む場合:
  • 無限再レンダリングやテーブルのフリーズが発生したとき
  • クエリデータがページネーション状態の変更と同期しないとき
  • サーバーサイド機能(ページネーション、フィルタリング、並べ替え)によって API が呼び出されないとき
  • Column Helper のインポートで TypeScript エラーが発生したとき
  • 並べ替え状態を変更しても API 呼び出しが更新されないとき
  • 1000 行を超えるクライアントサイドのテーブルでパフォーマンスに問題があるとき
  • 文書化された 6 件の問題に記載されているエラーメッセージが発生したとき
次の場合は references/server-side-patterns.md を読み込んでください:
  • API バックエンドでのページネーションの実装について質問された場合
  • バックエンドのクエリパラメーターを使ったフィルタリングの構築が必要な場合
  • データベースクエリに連動するソートを実装する場合
  • テーブルデータ用の Cloudflare Workers または任意の API エンドポイントを構築する場合
  • テーブルの状態(ページ、フィルター、ソート)とサーバー呼び出しを連携させる場合
  • manualPagination、manualFiltering、または manualSorting フラグについて質問された場合
次の場合は references/query-integration.md を読み込んでください:
  • TanStack Table と TanStack Query を連携させる場合
  • クエリーキーとテーブル状態の同期に関する問題がある場合
  • ページネーション/フィルター/ソートの変更時に再取得するパターンが必要な場合
  • テーブル状態を使ったクエリーキーの構成が必要な場合
  • サーバーサイドテーブルで古いデータに関する問題がある場合
次の場合は references/cloudflare-d1-examples.md を読み込んでください:
  • テーブルデータ用の Cloudflare Workers API エンドポイントを構築する場合
  • ページネーション/フィルタリングを含む D1 データベースクエリを書く場合
  • Cloudflare のエンドツーエンド統合例が必要な場合
  • テーブル機能向けの SQL クエリパターン(LIMIT/OFFSET、WHERE、ORDER BY)が必要な場合
  • D1 とテーブル向けの wrangler.jsonc バインディングを設定する場合
次の場合は references/performance-virtualization.md を読み込んでください:
  • クライアントサイドで 1000 行以上のデータセットを扱う場合
  • TanStack Virtual の統合について質問された場合
  • メモリー効率のよいレンダリングパターンが必要な場合
  • useVirtualizer() フックの使用方法を確認する場合
  • 大規模テーブルのパフォーマンスを最適化する場合
  • 行の仮想化やスクロールのパフォーマンスについて質問された場合
次の場合は references/feature-controls.md を読み込んでください:
  • 列の表示/非表示、固定、または並べ替えのコントロールが必要な場合
  • ツールバー(グローバル検索、切り替え)を構築する場合や、状態を URL/localStorage に同期する場合
  • ファジー検索/グローバル検索、またはファセットフィルターを実装する場合
  • 行選択/固定、または制御されたページネーション/ソートを設定する場合

トークン効率

このスキルを使用しない場合:

  • 約 8,000 トークン:v8 の変更点、サーバーサイドパターン、Query の統合を調査
  • よくあるエラーが 3~4 件発生
  • 合計 30~45 分

このスキルを使用する場合:

  • 約 3,500 トークン:すぐ使えるテンプレート、エラーの防止
  • エラー 0 件(文書化されている問題をすべて防止)
  • 10~15 分

削減効果: トークン約 55~65%、時間約 70%


本番環境での検証

次の環境でテスト済み:

  • 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)

スタックの互換性:

  • ✅ Cloudflare Workers + Static Assets
  • ✅ Cloudflare D1 データベース
  • ✅ TanStack Query の統合
  • ✅ React 19.2+ サーバーコンポーネント
  • ✅ TypeScript strict モード
  • ✅ Vite 7.3+ ビルド最適化

参考資料


最終更新日: 2025-12-09 スキルのバージョン: 1.1.0 ライブラリのバージョン: @tanstack/react-table v8.21.3

コメント