TanStack TableのReact Tableでリッチなテーブルを実装してみる
TanStack TableのReact Tableでリッチなテーブルを実装してみます。
TanStack | High Quality Open-Source Software for Web Developers
あるプロジェクトで脱jQueryの流れがあり、jQueryベースのプラグイン「DataTables」に代わるライブラリを探していて、React Tableにたどり着きました。
本記事の内容は、ほとんど公式サイトのガイド通り、かつ、雑な解説となっておりますが、、初導入時の記憶としてメモを残したいと思います。
- React 18.1
- React Table 8.2.2
ここではユーザ情報ををTableで表示する、シンプルな例を取り上げます。
また、コード量が多くなるので、要点を絞り紹介しています。
React Tableのインストール
npmコマンドを使ってReact Tableをインストールします。
$ npm install @tanstack/react-table
React Tableを設置する
まずは、ベーシックとなるテーブルを表示してみます。
ライブラリを読み込みます。
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
データの型定義とデータを用意します。
type User = {
firstName: string
lastName: string
age: number
}
const data: User[] = [
{
firstName: '太郎',
lastName: '山田',
age: 42,
},
{
firstName: '花子',
lastName: '山田',
age: 30,
},
...
]
カラムを定義します。
const columns: ColumnDef[] = [ { accessorKey: 'firstName', header: '名', }, { accessorKey: 'lastName', header: '姓', }, { accessorKey: 'age', header: '年齢', }, ]
テーブルを生成します。
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
})
実際に表示するJSXの部分のコードになります。
return (
<>
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.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>
</>
)
最低限になりますが、ひとまずこれでTableが表示されると思います。
ページャを設置する
データ数が数百、数千となると、テーブルが縦長になってしますので、ページングできるようにします。
ライブラリに「getPaginationRowModel」を追加します。
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
} from '@tanstack/react-table'
テーブルを生成する際に「getPaginationRowModel」を追加します。
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
JSXにページナビゲーション部分のHTMLを追記します。
<div>
<button onClick={() => table.setPageIndex(0)} disabled={!table.getCanPreviousPage()}>
{'<<'}
</button>
<button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
{'<'}
</button>
<button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
{'>'}
</button>
<button onClick={() => table.setPageIndex(table.getPageCount() - 1)} disabled={!table.getCanNextPage()}>
{'>>'}
</button>
<span>
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</strong>
</span>
<span>
| Go to page:
<input type="number" defaultValue={table.getState().pagination.pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
/>
</span>
<select value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
以上でページングが可能になります。
テーブルをソートする
次に、氏名や年齢でテーブルの行を並べ替えます。
ライブラリに「getPaginationRowModel」と「SortingState」を追加します。
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
getSortedRowModel,
SortingState,
} from '@tanstack/react-table'
ソートの状態を管理する変数が必要となるので、以下のように宣言します。
const [sorting, setSorting] = useState([])
テーブルを生成する際に、上記の変数と関数、そして「getSortedRowModel」を追加します。
const table = useReactTable({
data,
columns,
state: {
sorting,
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
})
JSXで、テーブルのヘッダ行にソートナビゲーション部分のHTMLを追記します。
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{{
asc: ' ▲',
desc: ' ▼',
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
以上でソートが可能になります。
ヘッダのカラム名のエリアをクリックするとそのカラムでソートされ、「▲」や「▼」が表示されます。
テーブルをフィルタリングする
次に、各カラムをフリーワードで検索してフィルタリングします。
ライブラリに「Column」、「Table」と「getFilteredRowModel」を追加します。
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
getSortedRowModel,
SortingState,
Column,
Table,
getFilteredRowModel,
} from '@tanstack/react-table'
テーブルを生成する際に「getFilteredRowModel」を追加します。
const table = useReactTable({
data,
columns,
state: {
sorting,
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
})
JSXで、テーブルのヘッダ行にフィルタリングのフォームコンポーネント「Filter」を追加します。
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{{
asc: ' ▲',
desc: ' ▼',
}[header.column.getIsSorted() as string] ?? null}
</div>
{header.column.getCanFilter() ? (
<div>
<Filter column={header.column} table={table} />
</div>
) : null}
</>
)}
</th>
)
})}
</tr>
))}
</thead>
さらに、Filterコンポーネントを定義します。
const Filter = ({
column,
table,
}:{
column: Column<any, any>
table: Table<any>
}) => {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id)
return typeof firstValue === 'number' ? (
<div>
<input
type="number"
value={((column.getFilterValue() as any)?.[0] ?? '') as string}
onChange={e =>
column.setFilterValue((old: any) => [e.target.value, old?.[1]])
}
placeholder={`Min`}
/>
<input
type="number"
value={((column.getFilterValue() as any)?.[1] ?? '') as string}
onChange={e =>
column.setFilterValue((old: any) => [old?.[0], e.target.value])
}
placeholder={`Max`}
/>
</div>
) : (
<input
type="text"
value={(column.getFilterValue() ?? '') as string}
onChange={e => column.setFilterValue(e.target.value)}
placeholder={`Search...`}
/>
)
}
ちなみに、フィルタリングのフォームは、カラム定義でオプション「accessorKey」を設定すると自動で表示されますが、オプション「enableColumnFilter」を追加して非表示設定することもできます。
const columns: ColumnDef[] = [ { accessorKey: 'firstName', header: '名', enableColumnFilter: false, }, (中略)
公式サイトのサンプルでは動作しないものもあり、導入するには試行錯誤しながら、結構な時間を要しました。
jQueryでは数行追記すれば高機能なテーブルを簡単に実装できていたのですが、Reactでは、比較的多くの時間とコードを費やしても、実現できる機能は十分ではありません。
ライブラリ移行は慎重なほうがよいかもしれません。
コメントする