ReactでTanstack Tableを使ったテーブル実装を試してみた
こんにちは。データアナリティクス事業本部 サービスソリューション部の北川です。
チーム内でTanstack Tableを使用する機会がありました。自分はまだ使ってませんが、今後使う可能性や、キャッチアップも兼ねて簡単にAPIを触ってみました。
Tanstack Tableとは
Tanstack Tableは、テーブルの作成を容易に実装できるヘッドレスUIライブラリになります。ヘッドレスなので柔軟性が高いのが特徴です。React Tableから移行されたライブラリであり、TypeScriptへ置き換わっているのが大きな変更点になります。
フィルタリングや、ソート、グルーピングなど機能も豊富です。今回は、その中でもページネーションのAPIを触ってみました。
試してみた
今回は、Next.jsを使用して、プロジェクトを作成します。
$ npx create-next-app sample-table --ts
プロジェクトに移動し、Tanstack Tableをインストールします。
$ yarn add @tanstack/react-table
テーブル作成
適当なファイルを作成して、テーブルの中身にあたるDATAと、列にあたるCOLUMNSを定義します。columnsのHeaderにはテーブルのヘッダーの値、accessorにはオブジェクトのキーを指定します。
import { ColumnDef } from "@tanstack/react-table"; type TData = { col1: string; col2: string; col3: number; }; export const DATA: TData[] = [ { col1: "Hello", col2: "World", col3: 1234, }, { col1: "react-table", col2: "rocks", col3: 5678, }, { col1: "whatever", col2: "you want", col3: 9012, }, ]; export const COLUMNS: ColumnDef<any>[] = [ { header: "Column 1", accessorKey: "col1", }, { header: "Column 2", accessorKey: "col2", }, { header: "Column 3", accessorKey: "col3", }, ];
useReactTable
フックを使用してテーブルをインスタンス化します。定義したDATAとCOLUMNSを、useReactTableに渡します。
pages/index.tsxを編集します。useMemoを使用して、データが不要に再レンダリングするのを防ぎます。
import type { NextPage } from "next"; import styles from "../styles/Home.module.css"; import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { COLUMNS, DATA } from "../libs/data"; import { useMemo } from "react"; const Home: NextPage = () => { const columns = useMemo(() => COLUMNS, []); const data = useMemo(() => DATA, []); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), }); return ( <div className={styles.container}> <main className={styles.main}> <table> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map((row) => { return ( <tr key={row.id}> {row.getVisibleCells().map((cell) => { return ( <td key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </td> ); })} </tr> ); })} </tbody> </table> </main> <p>{table.getRowModel().rows.length} Rows</p> </div> ); }; export default Home;
スタイリング
ヘッドレスなので、スタイルは完全にこちら側で制御します。今回は試すだけなので、global.cssファイルにcssを追記しました。
table { border: 1px solid lightgray; padding: 1px; } tbody { border-bottom: 1px solid lightgray; } th { border-bottom: 1px solid lightgray; padding: 4px; } td { padding: 4px; } tfoot { color: gray; } tfoot th { font-weight: normal; }
ページネーション
ページネーションの実装には、getPaginationRowModel
を使用します。
const table = useReactTable({ data: data, columns: columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), });
ページネーションの機能だけでも、多くのメソッドが用意されています(以下は一部)。
- getCanPreviousPage, getCanNextPage : 前のページ、次のページに移動できるかどうか真偽値を返す
-
nextPage、previousPage : 次のページに移動、前のページに移動する
-
setPageIndex : 指定のインデックスまで移動する
-
setPageSize : テーブルの表示領域を指定する
-
getPageCount : 全体のページ数を返す
pages/index.tsxを以下のように変更してみました。今回はuseSWRと、JSONPlaceholderを使用して、仮データを用意しています。
import type { NextPage } from "next"; import styles from "../styles/Home.module.css"; import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable, } from "@tanstack/react-table"; import { useMemo } from "react"; import useSWR from "swr"; const fetcher = (args: string) => fetch(args).then((res) => res.json()); type TData = { userId: number; id: number; title: string; body: string; }; const Home: NextPage = () => { const { data, isLoading } = useSWR( "https://jsonplaceholder.typicode.com/posts", fetcher ); const columns = useMemo<ColumnDef<TData>[]>( () => [ { header: "userId", accessorKey: "userId", }, { header: "Id", accessorKey: "id", }, { header: "Title", accessorKey: "title", }, ], [] ); const table = useReactTable({ data: data, columns: columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), debugTable: true, }); if (!table || isLoading) return <></>; return ( <main className={styles.main}> <table> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map((row) => { return ( <tr key={row.id}> {row.getVisibleCells().map((cell) => { return ( <td key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </td> ); })} </tr> ); })} </tbody> </table> <div style={{ margin: "5px" }}> <span>Page</span> <strong> {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} </strong> </div> <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> </div> <select style={{ margin: "10px" }} 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>{table.getRowModel().rows.length} Rows</div> </main> ); }; export default Home;
ページネーション機能も簡単に実装することができました。他にも、指定のページまで飛ぶためのメソッドなどが用意されています。
まとめ
今回は、Tanstack Tableを使用したテーブルの実装を試してみました。ドキュメントには、それぞれの機能を使った実装例ののsandboxが用意されているので、コードを通して使い方を学べるのはありがたかったです。
ではまた。