- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Message
- Message Scroller
- Native Select
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
| ステータス | メールアドレス | 金額 | ||
|---|---|---|---|---|
success | ken99@example.com | $316.00 | ||
success | Abe45@example.com | $242.00 | ||
processing | Monserrat44@example.com | $837.00 | ||
success | Silas22@example.com | $874.00 | ||
failed | carmella@example.com | $721.00 |
はじめに#
これまで作成したデータテーブルやデータグリッドはすべて固有のものでした。それぞれ動作が異なり、固有のソートやフィルタリング要件があり、異なるデータソースを扱います。
これらすべてのバリエーションを1つのコンポーネントにまとめるのは適切ではありません。そうすると、ヘッドレス UI が提供する柔軟性が失われます。
そこでデータテーブル コンポーネントそのものではなく、自分で構築する方法を説明するガイドを提供することにしました。
基本的な <Table /> コンポーネントから始め、複雑なデータテーブルをゼロから構築します。
ヒント: アプリ内の複数箇所で同じテーブルを使う場合は、再利用可能なコンポーネントとして切り出せます。
目次#
このガイドでは、TanStack Table と <Table /> コンポーネントを使ってカスタム データテーブルを構築する方法を説明します。次のトピックを扱います。
インストール#
<Table />コンポーネントをプロジェクトに追加します。
pnpm dlx shadcn@latest add table
@tanstack/react-table依存関係を追加します。このガイドでは TanStack Table v9 を使用します。
pnpm add @tanstack/react-table
前提条件#
最近の支払いを表示するテーブルを構築します。データは次のようになります。
type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
export const payments: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
{
id: "489e1d42",
amount: 125,
status: "processing",
email: "user@example.com",
},
// ...
]プロジェクト構成#
次のファイル構成を作成することから始めます。
app
└── payments
├── columns.tsx
├── data-table-features.ts
├── data-table.tsx
└── page.tsxここでは Next.js の例を使用しますが、他の React フレームワークでも動作します。
columns.tsx(クライアント コンポーネント)には列定義を記述します。data-table-features.tsには、TanStack Table で有効にする動作を指定する共有featuresオブジェクトを記述します。data-table.tsx(クライアント コンポーネント)には<DataTable />コンポーネントを記述します。page.tsx(サーバー コンポーネント)でデータを取得し、テーブルを描画します。
テーブル機能を設定する#
TanStack Table v9 は機能ベースです。tableFeatures() でソート、フィルタリング、ページネーションなど必要な動作を宣言して有効にします。一覧に含めないものはバンドルから tree-shaking されます。組み込みのフィルター関数とソート関数も対象なので、列が使用するものを filterFns と sortFns に登録します(メール フィルターは includesString を使い、文字列列は text / alphanumeric でソートします)。
import {
columnFilteringFeature,
columnVisibilityFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFn_includesString,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
sortFn_alphanumeric,
sortFn_text,
tableFeatures,
} from "@tanstack/react-table"
// v9 では、このテーブルで使用する機能を宣言します。登録しない機能は
// バンドルから tree-shaking されます。
export const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
filterFns: { includesString: filterFn_includesString },
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
})
// Pass this as the first generic argument to `ColumnDef`, `Column`, `Table`,
// and `Row` so each type knows which feature APIs are available.
export type DataTableFeatures = typeof features注: コア行モデルは常に含まれるため、自分で登録する必要はありません。任意機能の行モデルは create*RowModel() で作成して features オブジェクトに登録します。get*RowModel のテーブル オプションはもうありません。
基本的なテーブル#
基本的なテーブルを構築します。
列の定義#
まず列を定義します。
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { type DataTableFeatures } from "./data-table-features"
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
// Use `accessor` for data columns and `display` for columns without one.
const columnHelper = createColumnHelper<DataTableFeatures, Payment>()
export const columns = columnHelper.columns([
columnHelper.accessor("status", {
header: "ステータス",
}),
columnHelper.accessor("email", {
header: "メールアドレス",
}),
columnHelper.accessor("amount", {
header: "金額",
}),
])注: 列では、テーブルの見た目の中心となる部分を定義します。表示するデータと、 その書式、並べ替え、フィルタリングの方法を指定します。
<DataTable /> コンポーネント#
次に、テーブルを描画する <DataTable /> コンポーネントを作成します。
"use client"
import { useTable, type ColumnDef, type RowData } from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { features, type DataTableFeatures } from "./data-table-features"
interface DataTableProps<TData extends RowData> {
columns: ColumnDef<DataTableFeatures, TData>[]
data: TData[]
}
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const table = useTable({
features,
data,
columns,
})
return (
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getFlatHeaders().map((header) => (
<TableHead
key={header.id}
id={header.id}
isRowHeader={header.index === 0}
>
{header.isPlaceholder ? null : (
<table.FlexRender header={header} />
)}
</TableHead>
))}
</TableHeader>
<TableBody renderEmptyState={() => "結果がありません。"}>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id} id={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}ヒント: <DataTable /> を複数箇所で使う場合は、このコンポーネントを components/ui/data-table.tsx に切り出して再利用できます。
<DataTable columns={columns} data={data} />
<table.FlexRender /> と flexRender: このガイドでは、テーブル インスタンスから直接利用でき、追加のインポートが不要な v9 の <table.FlexRender header={header} /> と <table.FlexRender cell={cell} /> コンポーネントを使用します。関数形式を好む場合(または table を所有するコンポーネントの外側でレンダリングする必要がある場合)は、v8 の従来の flexRender(component, context) ヘルパーも引き続き使用できます。その場合は単独の <FlexRender /> もインポートできます。
テーブルをレンダリングする#
最後に、ページ コンポーネントでテーブルをレンダリングします。
import { columns, Payment } from "./columns"
import { DataTable } from "./data-table"
async function getData(): Promise<Payment[]> {
// ここで API からデータを取得します。
return [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
// ...
]
}
export default async function DemoPage() {
const data = await getData()
return (
<div className="container mx-auto py-10">
<DataTable columns={columns} data={data} />
</div>
)
}セルの書式設定#
金額セルをドル表示に書式設定し、右揃えにします。
列の定義を更新する#
金額の header と cell の定義を次のように更新します。
export const columns = columnHelper.columns([
columnHelper.accessor("amount", {
header: () => <div className="text-right">金額</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount"))
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount)
return <div className="text-right font-medium">{formatted}</div>
},
}),
])同じ方法で、他のセルやヘッダーも書式設定できます。
行のアクション#
テーブルに行のアクションを追加します。ここでは <Dropdown /> コンポーネントを使用します。
列定義を更新#
列の定義を更新して新しい actions 列を追加します。actions セルは <Dropdown /> コンポーネントを返します。
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export const columns = columnHelper.columns([
// ...
columnHelper.display({
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-xs">
<span className="sr-only">メニューを開く</span>
<MoreHorizontal />
</Button>
<DropdownMenu placement="bottom end" className="w-44">
<DropdownMenuGroup>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
)
},
}),
// ...
])cell 関数では row.original を使って行のデータにアクセスできます。これを使って行のアクションを処理します。たとえば id を使って API に DELETE リクエストを送信できます。
ページネーション#
次に、テーブルへページネーションを追加します。
ページネーションはすでに有効#
features オブジェクトには rowPaginationFeature と createPaginatedRowModel() が含まれているため、テーブルは行を自動的に 10 件ずつのページに分割します。useTable に追加するものはありません。ページ サイズのカスタマイズや手動ページネーションについては、ページネーションのドキュメントを参照してください。
ページネーションコントロールを追加する#
<Button /> コンポーネントと table.previousPage()、table.nextPage() API メソッドを使って、テーブルにページネーション コントロールを追加できます。
import { Button } from "@/components/ui/button"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const table = useTable({
features,
data,
columns,
})
return (
<div>
<div className="overflow-hidden rounded-md border">
<Table>
{ // .... }
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}より高度なページネーション コンポーネントについては、再利用可能なコンポーネントを参照してください。
並べ替え#
メールアドレス列を並べ替え可能にします。
rowSortingFeature とソート済み行モデルはすでに features オブジェクトに登録されています。あとは並べ替えの状態を接続するだけです。
<DataTable> を更新する#
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type RowData,
type SortingState,
} from "@tanstack/react-table"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
state: {
sorting,
},
})
return (
<div>
<div className="overflow-hidden rounded-md border">
<Table
sortDescriptor={
sorting.length
? {
column: sorting[0].id,
direction: sorting[0].desc ? "descending" : "ascending",
}
: undefined
}
onSortChange={(sortDescriptor) => {
table.setSorting([
{
id: "" + sortDescriptor.column,
desc: sortDescriptor.direction === "descending",
},
]);
}}>
{ ... }
</Table>
</div>
</div>
)
}ヘッダーセルを並べ替え可能にする#
これで email ヘッダー セルを更新し、並べ替えコントロールを追加できます。
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { ArrowUpDown } from "lucide-react"
import { buttonVariants } from "@/components/ui/button"
export const columns = columnHelper.columns([
columnHelper.accessor("email", {
header: ({ column }) => {
return (
<div className={buttonVariants({ variant: "ghost" })}>
Email
<ArrowUpDown className="ml-2 h-4 w-4" />
</div>
)
},
}),
])ヘッダー セルを切り替えると、テーブルが自動的に昇順・降順で並べ替えられます。
フィルタリング#
テーブルのメールアドレスを絞り込む検索入力を追加します。
columnFilteringFeature とフィルター済み行モデルはすでに features オブジェクトに含まれているため、フィルター状態を接続して入力欄を表示するだけで済みます。
<DataTable> を更新する#
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type ColumnFiltersState,
type RowData,
type SortingState,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
state: {
sorting,
columnFilters,
},
})
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder="メールアドレスを絞り込む..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
)
}これで email 列のフィルタリングが有効になりました。他の列にもフィルターを追加できます。フィルターのカスタマイズについては、フィルタリングのドキュメントを参照してください。
表示状態#
@tanstack/react-table の表示状態 API を使えば、列の表示・非表示を簡単に追加できます。
<DataTable> を更新する#
"use client"
import * as React from "react"
import {
useTable,
type ColumnDef,
type ColumnFiltersState,
type ColumnVisibilityState,
type RowData,
type SortingState,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
state: {
sorting,
columnFilters,
columnVisibility,
},
})
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder="メールアドレスを絞り込む..."
value={table.getColumn("email")?.getFilterValue() as string}
onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenuTrigger>
<Button variant="outline" className="ml-auto">
Columns
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuGroup
selectionMode="multiple"
selectedKeys={
table
.getVisibleFlatColumns()
.filter((column) => column.getCanHide())
.map(column => column.id)
}
onSelectionChange={(keys) => {
table.setColumnVisibility(
Object.fromEntries(
table
.getAllFlatColumns()
.map((c) => [
c.id,
!c.getCanHide() || keys === "all" || keys.has(c.id),
])
)
)
}}
>
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuItem
key={column.id}
id={column.id}
className="capitalize"
>
{column.id}
</DropdownMenuItem>
)
})}
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</div>
<div className="overflow-hidden rounded-md border">
<Table>{ ... }</Table>
</div>
</div>
)
}これで、列の表示・非表示を切り替えるドロップダウン メニューが追加されます。
行の選択#
次に、テーブルへ行の選択を追加します。
列定義を更新#
"use client"
import { createColumnHelper } from "@tanstack/react-table"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
export const columns = columnHelper.columns([
columnHelper.display({
id: "select",
header: () => <Checkbox slot="selection" />,
cell: () => <Checkbox slot="selection" />,
enableSorting: false,
enableHiding: false,
}),
])<DataTable> を更新する#
export function DataTable<TData extends RowData>({
columns,
data,
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const [rowSelection, setRowSelection] = React.useState({})
const table = useTable({
features,
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div>
<div className="overflow-hidden rounded-md border">
<Table
selectionMode="multiple"
selectedKeys={table.getSelectedRowModel().rows.map((row) => row.id)}
onSelectionChange={(selection) => {
if (selection === "all") {
table.toggleAllRowsSelected()
} else {
table.setRowSelection(
Object.fromEntries([...selection].map((key) => [key, true]))
)
}
}}
/>
</div>
</div>
)
}各行にチェックボックスが追加され、ヘッダーのチェックボックスで全行を選択できるようになります。
選択した行を表示#
table.getFilteredSelectedRowModel() API を使って、選択した行数を表示できます。
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>再利用可能なコンポーネント#
データテーブルの構築に使えるコンポーネントを紹介します。これは Tasks デモのもので、data-table-features.ts モジュールを通じて、すべてのコンポーネントで features オブジェクト(対応する TasksTableFeatures 型も含む)を共有しています。テーブル機能の設定で構築したものと同じパターンです。
列ヘッダー#
任意の列ヘッダーを並べ替え可能・非表示可能にします。
export const columns = columnHelper.columns([
columnHelper.accessor("email", {
header: ({ column }) => (
<DataTableColumnHeader column={column} title="メールアドレス" />
),
}),
])ページネーション#
ページ サイズと選択数を含むページネーション コントロールをテーブルに追加します。
<DataTablePagination table={table} />列の切り替え#
列の表示・非表示を切り替えるコンポーネントです。
<DataTableViewOptions table={table} />RTL#
shadcn/ui で RTL サポートを有効にするには、RTL 構成ガイドを参照してください。
| الحالة | البريد الإلكتروني | المبلغ | ||
|---|---|---|---|---|
ناجح | ken99@example.com | ٣١٦٫٠٠ US$ | ||
ناجح | Abe45@example.com | ٢٤٢٫٠٠ US$ | ||
قيد المعالجة | Monserrat44@example.com | ٨٣٧٫٠٠ US$ | ||
ناجح | Silas22@example.com | ٨٧٤٫٠٠ US$ | ||
فشل | carmella@example.com | ٧٢١٫٠٠ US$ |
On This Page
はじめに目次インストール前提条件プロジェクト構成テーブル機能を設定する基本的なテーブル列の定義<DataTable /> コンポーネントテーブルをレンダリングするセルの書式設定列の定義を更新する行のアクション列定義を更新ページネーションページネーションはすでに有効ページネーションコントロールを追加する並べ替え<DataTable> を更新するヘッダーセルを並べ替え可能にするフィルタリング<DataTable> を更新する表示状態<DataTable> を更新する行の選択列定義を更新<DataTable> を更新する選択した行を表示再利用可能なコンポーネント列ヘッダーページネーション列の切り替えRTL