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では、比較的多くの時間とコードを費やしても、実現できる機能は十分ではありません。
ライブラリ移行は慎重なほうがよいかもしれません。
コメントする