tech-book-labs
ガイド(データユーティリティ) · 2 セクション統合

TanStack Table v8 実装ガイド

TanStack Table v8(headless table)で柔軟なテーブル UI を組むパターン。最小実装、行選択 / リサイズの実装までを 1 ページに統合。

著者:TechBook.net編集部 · 最終検証 2026-05-10
セクション · 2026-05-10 · @tanstack/react-table 8.x

TanStack Table v8 で headless にテーブルを組む

TanStack Table v8(@tanstack/react-table)で sort / filter / pagination を実装する最小コード、core / sorted / filtered Row Model の組み合わせ方を触れる demo で確認する実装メモ。

検証日: 2026-05-09

使用バージョン: @tanstack/react-table@8.x

対象: React + TypeScript でデータテーブルが必要、自前 UI を当てたい人

TanStack Table v8 を headless(state とロジックだけ提供、UI は呼び出し側で組む)で扱う基本パターン。ColumnDef<T> の型付き定義、sort / filter / pagination の組み合わせ、Row Model の概念を動く demo で確認します。

触って試す

IDNameRoleCommits
1Alicefrontend142
2Bobbackend88
3Charliedevops211
4Danafrontend65
5Erindata197
6Frankbackend34
ヘッダクリックでソート / 上のテキストで全列フィルタ

ヘッダクリックで sort、上のテキストボックスで全列フィルタ。

なぜ headless か

TanStack Table は UI を提供しない。代わりに「state(sort 状態 / filter 値 / page index)を管理し、表示すべき rows の配列を返す」だけ。スタイルは Tailwind / CSS で自前。

メリット:

  • <table> の構造を完全に自分で書ける(意味的 HTML、a11y、印刷スタイル等)
  • デザインシステム(shadcn / MUI / 自社)との衝突がない
  • bundle サイズが小さい(15KB 前後 min+gzip)

代わりに、ボイラープレートは多め。

最小サンプル

ColumnDef<T> 配列 + useReactTable から table.getRowModel() を取って、テーブル DOM を組む最小コード:

import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";

type User = { id: number; name: string; commits: number };

const data: User[] = [
  { id: 1, name: "Alice", commits: 142 },
  { id: 2, name: "Bob", commits: 88 },
];

const columns: ColumnDef<User>[] = [
  { accessorKey: "id", header: "ID" },
  { accessorKey: "name", header: "Name" },
  { accessorKey: "commits", header: "Commits" },
];

export function Table() {
  const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((hg) => (
          <tr key={hg.id}>
            {hg.headers.map((h) => (
              <th key={h.id}>{flexRender(h.column.columnDef.header, h.getContext())}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

ソートを足す

sorting state を useState で持ち、getSortedRowModel() を有効化 + <th onClick={header.column.getToggleSortingHandler()}> で列クリックで sort:

import { getSortedRowModel, SortingState } from "@tanstack/react-table";

const [sorting, setSorting] = useState<SortingState>([]);

const table = useReactTable({
  data, columns,
  state: { sorting },
  onSortingChange: setSorting,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),  // ← 追加
});

// header 描画で
<th onClick={h.column.getToggleSortingHandler()}>
  {flexRender(h.column.columnDef.header, h.getContext())}
  {h.column.getIsSorted() === "asc" ? " ▲" : h.column.getIsSorted() === "desc" ? " ▼" : ""}
</th>

グローバルフィルタ

検索ボックスで全列を横断する filter。globalFilter state + getFilteredRowModel() を組み合わせる:

import { getFilteredRowModel } from "@tanstack/react-table";

const [filter, setFilter] = useState("");

const table = useReactTable({
  data, columns,
  state: { sorting, globalFilter: filter },
  onGlobalFilterChange: setFilter,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
});

<input value={filter} onChange={(e) => setFilter(e.target.value)} />

ページネーション

getPaginationRowModel() を有効化。table.previousPage() / nextPage() でページ送り、getCanPreviousPage / getCanNextPage で先頭・末尾判定:

import { getPaginationRowModel } from "@tanstack/react-table";

const table = useReactTable({
  // ...
  initialState: { pagination: { pageSize: 10 } },
  getPaginationRowModel: getPaginationRowModel(),
});

<button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>前</button>
<span>{table.getState().pagination.pageIndex + 1} / {table.getPageCount()}</span>
<button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>次</button>

つまずいたポイント

  • get*RowModel() を register しないと機能しない:getSortedRowModel を渡し忘れると sort UI が動かない
  • flexRender を忘れると JSX が出ない:cell / header 描画は必ず flexRender 経由
  • accessorKeyaccessorFn の使い分け:ネスト値や computed は fn で取り出す
  • sort indicator は自前:▲ ▼ や aria-sort は自分で書く必要あり
  • virtualization は別ライブラリ(@tanstack/react-virtual を組み合わせ)。1 万行超は virtuoso 系を検討

評価

観点評価コメント
学習コスト概念(Row Model)が独特、最初の 1 つを書くまでは時間
型サポートColumnDef<T> で全 cell が型推論
カスタマイズ性完全 headless、UI を制約しない
機能sort / filter / pagination / グルーピング / expand / row selection — column resize や行範囲選択は /articles/tanstack-table-v8-resize-selection/
バンドル15KB 前後

向く / 向かないケース

  • 向く: デザインシステムが固まっているプロジェクト、admin パネル、ダッシュボード、CRUD 画面
  • 向かない: 「数行コードで動くテーブル UI が欲しい」(MUI X DataGrid / AG Grid のような複合 UI 製品)
  • 向かない: 1 万行超の virtual scroll(react-virtuoso との合成 or別ライブラリ)

関連 Topic / 関連書籍

この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:

tech-book.net /books/9784839966645

React Native+Expoではじめるスマホアプリ開発 : JavaScriptによるアプリ構築の実際

松澤 太郎 · マイナビ出版 · 2018年

「React Native」は、Facebookが開発しているスマートフォンアプリ向けの開発環境です。ほとんどのコードをJav…

詳細を tech-book.net で見る
tech-book.net /books/9784873119380

Reactハンズオンラーニング 第2版 : Webアプリケーション開発のベストプラクティス

Alex Banks/Eve Porcello/宮崎 空 · オライリー・ジャパン · 2021年 · ¥3,740

Webフロントエンドの「今」を学びたい人へ! Facebookが開発したJavaScrip…

詳細を tech-book.net で見る
tech-book.net /books/9784873117881

Reactビギナーズガイド : コンポーネントベースのフロントエンド開発入門

Stoyan Stefanov/牧野 聡 · オライリー・ジャパン · 2017年 · ¥2,750

FacebookのエンジニアによるReactの入門書! ReactによるコンポーネントベースのWebフロントエンド開発の…

詳細を tech-book.net で見る
tech-book.net /books/9784297129163

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発

手島 拓也/吉田 健人/高林 佳稀 · 技術評論社 · 2022年

新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…

詳細を tech-book.net で見る
tech-book.net /books/9784844379546

【POD】React &amp; Gatsby開発入門

竹本 雄貴
詳細を tech-book.net で見る
セクション · 2026-05-10 · @tanstack/react-table 8.x

TanStack Table v8 で列幅 + 列表示 + 行選択 + ページング

TanStack Table v8 の踏み込んだ機能 — column resize / column visibility / row selection / pagination を 1 つのテーブルで組み合わせる実装パターンを触れる demo で確認する実装メモ。

検証日: 2026-05-10

使用バージョン: @tanstack/react-table@8.x

対象: 入門は通っている、admin / dashboard 級のテーブル UI を組みたい人

TanStack Table v8 で column resize / row selection(範囲含む) を組むパターン。enableColumnResizing + columnResizeModerowSelection state、shift-click による範囲選択、を動く demo で確認します。

触って試す

列の表示:
IDNameRoleCommitsReviewsUpdated
1Alicefrontend842026-05-19
2Bobbackend39212026-05-16
3Charliedevops70382026-05-13
4Danadata101552026-05-10
5Erindesign132722026-05-07
6Frankfrontend16392026-05-04
7Gracebackend194262026-05-01
8Alicedevops225432026-04-28
18 /47
1 / 6
ヘッダ右端の縦線をドラッグで列幅変更 / チェックボックスで列の表示切替 / 行のチェックで選択

ヘッダ右端の縦線を ドラッグで列幅変更、上のチェックボックスで 列表示切替、行のチェックボックスで 選択、下のボタンで ページング

機能を 1 つの table に同居させる

TanStack Table の機能はそれぞれ:

  • state 管理(useState)
  • state を useReactTablestate に渡す
  • 対応する on*Change ハンドラを渡す
  • 対応する get*RowModel を register
  • JSX で対応する getter を呼ぶ

の 5 ステップ揃えて初めて動く。最小化したサンプル:

import {
  type ColumnDef,
  type SortingState,
  type RowSelectionState,
  type VisibilityState,
  type ColumnSizingState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table";

function FullFeatureTable({ data }: { data: Row[] }) {
  const [sorting, setSorting] = useState<SortingState>([]);
  const [filter, setFilter] = useState("");
  const [colSize, setColSize] = useState<ColumnSizingState>({});
  const [colVis, setColVis] = useState<VisibilityState>({});
  const [rowSel, setRowSel] = useState<RowSelectionState>({});

  const table = useReactTable({
    data,
    columns,
    state: { sorting, globalFilter: filter, columnSizing: colSize, columnVisibility: colVis, rowSelection: rowSel },
    onSortingChange: setSorting,
    onGlobalFilterChange: setFilter,
    onColumnSizingChange: setColSize,
    onColumnVisibilityChange: setColVis,
    onRowSelectionChange: setRowSel,
    columnResizeMode: "onChange",
    enableRowSelection: true,
    initialState: { pagination: { pageSize: 8 } },
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  });

  // ... render
}

1. Column Resize(列幅ドラッグ)

enableColumnResizing を有効にして <th> の右端に drag ハンドル を置き、header.getResizeHandler() を mouse/touch event に bind:

const table = useReactTable({
  ...,
  columnResizeMode: "onChange",   // または "onEnd"
});

// header 描画
<th key={h.id} style={{ width: h.getSize(), position: "relative" }}>
  {flexRender(h.column.columnDef.header, h.getContext())}
  {h.column.getCanResize() && (
    <span
      onMouseDown={h.getResizeHandler()}
      onTouchStart={h.getResizeHandler()}
      style={{
        position: "absolute", right: 0, top: 0, height: "100%",
        width: 6, cursor: "col-resize",
        background: h.column.getIsResizing() ? "blue" : "transparent",
      }}
    />
  )}
</th>

ポイント:

  • columnResizeMode: "onChange" = ドラッグ中もリアルタイム反映、"onEnd" = ドラッグ終了時に確定
  • <th>position: relative、resize handle は position: absolute で右端
  • handle width は 6px 程度、touch にも反応するよう onTouchStart も繋ぐ
  • 特定列を不可にする:enableResizing: false(チェックボックス列など)

2. Column Visibility(列の表示切替)

columnVisibility state を useState で持ち、各列の表示 / 非表示を toggle するチェックボックスを描く:

{table.getAllLeafColumns().map((col) => (
  <label key={col.id}>
    <input
      type="checkbox"
      checked={col.getIsVisible()}
      onChange={col.getToggleVisibilityHandler()}
    />
    {String(col.columnDef.header)}
  </label>
))}

getAllLeafColumns() で全列を列挙。getToggleVisibilityHandler() で onChange handler を取得して input に渡す。

3. Row Selection(チェックボックスで行選択)

rowSelection state を有効化し、ヘッダ行に「全選択」、各行にチェックボックスを置く。getIsAllPageRowsSelected / getIsSomePageRowsSelected で indeterminate 状態も判定:

// 「全選択」のヘッダセル
header: ({ table }) => (
  <input
    type="checkbox"
    checked={table.getIsAllRowsSelected()}
    onChange={table.getToggleAllRowsSelectedHandler()}
  />
),

// 各行のセル
cell: ({ row }) => (
  <input
    type="checkbox"
    checked={row.getIsSelected()}
    onChange={row.getToggleSelectedHandler()}
  />
),

選択数の取得:

const selectedCount = Object.values(rowSel).filter(Boolean).length;

選択された Row の data を一括取得:

const selectedRows = table.getSelectedRowModel().rows.map((r) => r.original);

4. Pagination

getPaginationRowModel() を有効化し、table.setPageSizetable.previousPage / nextPage でページ操作を組む:

const table = useReactTable({
  ...,
  initialState: { pagination: { pageSize: 8 } },
  getPaginationRowModel: getPaginationRowModel(),
});

// JSX
<button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>前</button>
<span>{table.getState().pagination.pageIndex + 1} / {table.getPageCount()}</span>
<button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>次</button>

サーバー側ページングしたい場合は manualPagination: true + pageCount を渡す。

5. ColumnDef でセルのカスタムレンダリング

columnscell プロパティに JSX を返す関数を渡せば、セル単位で表示を組み替えできる(アイコン / リンク / バッジ等):

const columns: ColumnDef<Row>[] = [
  { accessorKey: "id", header: "ID", size: 60 },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ getValue }) => {
      const v = getValue<string>();
      return <span className={v === "ok" ? "text-green-600" : "text-red-600"}>{v}</span>;
    },
  },
  {
    id: "actions",
    header: "",
    cell: ({ row }) => <button onClick={() => del(row.original.id)}>削除</button>,
    enableResizing: false,
    enableSorting: false,
  },
];

accessorKey で値直結、cell で表示カスタマイズ、accessorFn で計算列も書ける。

つまずいたポイント

  • <table>widthgetCenterTotalSize() で動的指定:列幅を合計した width を table 全体に設定しないと、resize 後に拡縮しない
  • overflow: auto を親 div に:列を広げると table が親より大きくなり、横スクロールが必要
  • select 列は enableResizing: false + enableSorting: false で誤操作防止
  • onRowSelectionChange の値型は Record<string, boolean>:row.id ではなく row index を key にした boolean マップ
  • getRowId を渡すと選択状態が永続化しやすい:getRowId: (row) => String(row.id) で安定 ID
  • virtual scroll と組み合わせる時は @tanstack/react-virtual を別途追加(react-virtuoso よりこちらが Table と相性よい)

関連 Topic / 関連書籍

この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:

tech-book.net /books/9784839966645

React Native+Expoではじめるスマホアプリ開発 : JavaScriptによるアプリ構築の実際

松澤 太郎 · マイナビ出版 · 2018年

「React Native」は、Facebookが開発しているスマートフォンアプリ向けの開発環境です。ほとんどのコードをJav…

詳細を tech-book.net で見る
tech-book.net /books/9784873119380

Reactハンズオンラーニング 第2版 : Webアプリケーション開発のベストプラクティス

Alex Banks/Eve Porcello/宮崎 空 · オライリー・ジャパン · 2021年 · ¥3,740

Webフロントエンドの「今」を学びたい人へ! Facebookが開発したJavaScrip…

詳細を tech-book.net で見る
tech-book.net /books/9784873117881

Reactビギナーズガイド : コンポーネントベースのフロントエンド開発入門

Stoyan Stefanov/牧野 聡 · オライリー・ジャパン · 2017年 · ¥2,750

FacebookのエンジニアによるReactの入門書! ReactによるコンポーネントベースのWebフロントエンド開発の…

詳細を tech-book.net で見る
tech-book.net /books/9784297129163

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発

手島 拓也/吉田 健人/高林 佳稀 · 技術評論社 · 2022年

新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…

詳細を tech-book.net で見る
tech-book.net /books/9784844379546

【POD】React &amp; Gatsby開発入門

竹本 雄貴
詳細を tech-book.net で見る

全ガイドは ガイド一覧 から。連載は 連載一覧 へ。