TanStack TableのstateをURLパラメータに同期するライブラリを作った
Tanstack TableはReact, Vue, Solidなどの様々なライブラリ/フレームワークで使用できる、高機能なテーブルを作るためのHeadlessなUIライブラリです。
今回は、ReactでTanStack Tableを使用する際に検索やソートなどの状態をURLパラメータに同期できるtanstack-table-search-paramsというライブラリを作ったのでその紹介です。
TanStack Table
まずReactでのTanStack Tableの使い方を簡単に紹介します。
TanStack TableではuseReactTable
というhookに、テーブルに表示するデータやカラムの定義などを渡し、その戻り値からテーブルのstateやハンドラーなどにアクセスしてテーブルを作ります。
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
type User = { id: string; name: string };
// テーブルに表示するデータ
const data: User[] = [
{ id: "1", name: "John" },
{ id: "2", name: "Sara" },
];
// カラムの定義
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.accessor("id", {}),
columnHelper.accessor("name", {}),
];
export default function UserTable() {
// テーブルを作成
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div>
<table>
<thead>
<tr>
{table.getFlatHeaders().map((header) => (
<th key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
</thead>
<tbody>
{/* テーブルのstateにアクセスして表示する */}
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getAllCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
さらにこのテーブルに検索を実装したのが以下です。
import {
// ...
+ getFilteredRowModel,
} from "@tanstack/react-table";
// ...
export default function UserTable() {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
});
return (
<div>
+ <input
+ placeholder="Search..."
+ value={table.getState().globalFilter}
+ onChange={(e) => table.setGlobalFilter(e.target.value)}
+ />
<table>
ここでgetCoreRowModel
とgetFilteredRowModel
は、テーブルの各機能を実装したRowModelという関数で、TanStack Tableでは@tanstack/react-table
からimportできるgetFilteredRowModel
のような組み込みのRowModelか、自作したRowModelを使ってテーブルに検索やソートなどの機能を追加できます。
tanstack-table-search-params
では次に今回作成したtanstack-table-search-paramsを使って、検索の状態をURLパラメータに同期します。
以下はNext.jsのPages Routerの例です。
tanstack-table-search-paramsでは、
-
useTableSearchParams
をimportして、Next.jsのuseRouter
の戻り値のオブジェクトを引数に渡して呼び出す -
useTableSearchParams
の戻り値をuseReactTable
の引数に渡す
だけで、TanStack TableのstateをURLパラメータに同期できます。
+ import { useRouter } from "next/router";
+ import { useTableSearchParams } from "tanstack-table-search-params";
// ...
export default function UserTable() {
+ const router = useRouter();
+ const stateAndOnChanges = useTableSearchParams(router);
const table = useReactTable({
+ ...stateAndOnChanges,
// ...
URLパラメータに同期する仕組み
tanstack-table-search-paramsでTanStack TableのstateをURLパラメータに同期する仕組みを紹介するために、上のコードでrouter
やstateAndOnChanges
のオブジェクトを丸ごと受け渡す部分を、必要なプロパティに絞った形式に変更します。
export default function UserTable() {
const { query, pathname, replace} = useRouter();
const stateAndOnChanges = useTableSearchParams({
query: router.query,
pathname: router.pathname,
replace: router.replace,
});
const table = useReactTable({
state: {
globalFilter: stateAndOnChanges.state.globalFilter,
},
onGlobalFilterChange: stateAndOnChanges.onGlobalFilterChange,
// ...
ここでuseReactTable
にstate.globalFilter
とonGlobalFilterChange
を渡しているのは、TanStack TableのstateをuseReactTable
の外部で管理する場合の設定方法です。
上のコードの通り、useTableSearchParams
は
-
query
: URLパラメータのReact state -
pathname
: 現在のURLのパス -
replace
(orpush
)
を受け取って、
state.globalFilter
onGlobalFilterChange
を返すhookです。
引数と戻り値を見ていただけたら想像がつくかもしれませんが、このhookでやっていることはとてもシンプルです。
まずstate
は、query
をTanStack Tableの以下のTableState
型に変換(デコード)して返しています。
次にonGlobalFilterChange
は、各TanStack TableのstateをURLパラメータに変換(エンコード)してreplace
を実行する関数を返しているだけです。
state
とonGlobalFilterChange
を作る部分は、どちらもただの変換(オブジェクト→オブジェクト, 関数→関数)で、内部で別のstateなどを持っていないのが特徴です。(※後述するdebounceでのみ、内部でdebounce用のstateを持っています)
対応しているTanStack Tableのstate
現在(2024/11/20時点)は、以下の4つのTanStack Tableのstateに対応していますが、今後も増やしていく予定です。
- globalFilter: 行全体の検索
- sorting: ソート
- pagination: ページネーション
- columnFilters: 列ごとの検索
使用できるrouter
URLパラメータに同期する仕組みで紹介したように、tanstack-table-search-paramsはURLパラメータのReact stateをTanStack Tableのstateに変換しているだけなので、ReactのstateでURLパラメータを取得できるrouterであれば(たぶん)使用できます。
以下の3つは、exampleを用意しているのでよかったらご覧ください。
その他設定できること
tanstack-table-search-paramsでは、URLパラメータ名やエンコード形式などをカスタマイズできます。
URLパラメータ名
URLパラメータ名はデフォルトではglobalFilter
、sorting
のようなTableState
型と同じ名前が使われます。
URLパラメータ名を変更する場合は、useTableSearchParams
の第2引数のparamNames
で指定できます。
const stateAndOnChanges = useTableSearchParams(router, {
paramNames: {
// URLパラメータ名を変更
globalFilter: "search",
},
});
prefixやsuffixを追加する場合は、関数を渡すこともできます。
const stateAndOnChanges = useTableSearchParams(router, {
paramNames: {
// URLパラメータ名にprefixをつける
globalFilter: (defaultName) => `userTable-${defaultName}`,
},
});
エンコード形式
URLパラメータの値のエンコード形式はデフォルトでは、以下のような素朴な形式です。
stateの値 | URLパラメータの例 |
---|---|
行全体の検索 | ?globalFilter=John |
ソート | ?sorting=name.desc |
ページネーション | ?pageIndex=2&pageSize=20 |
エンコード形式を変更する場合は、useTableSearchParams
の第2引数のencoders
とdocoders
で指定できます。
const stateAndOnChanges = useTableSearchParams(router, {
encoders: {
// エンコード形式をJSON.stringifyに変更
globalFilter: (globalFilter) => ({
globalFilter: JSON.stringify(globalFilter),
}),
},
decoders: {
globalFilter: (query) =>
query["globalFilter"]
? JSON.parse(query["globalFilter"])
: (query["globalFilter"] ?? ""),
},
});
エンコード形式とパラメータ名の両方の変更も可能です。
const stateAndOnChanges = useTableSearchParams(router, {
encoders: {
sorting: (sorting) => ({
// エンコード形式をJSON.stringifyに変更して、パラメータ名をmy-sortingに変更
"my-sorting": JSON.stringify(sorting),
}),
},
decoders: {
sorting: (query) =>
query["my-sorting"]
? JSON.parse(query["my-sorting"])
: query["my-sorting"],
},
});
debounce
URLパラメータへの同期をdebounceさせることもできます。
const stateAndOnChanges = useTableSearchParams(router, {
debounceMilliseconds: 500,
});
特定のTanStack Tableのstateのみのdebounceも可能です。
const stateAndOnChanges = useTableSearchParams(router, {
debounceMilliseconds: {
// globalFilterのURLパラメータへの同期を0.5秒debounceさせる
globalFilter: 500,
},
});
デフォルト値
URLパラメータが存在しない場合のデフォルトのTanStack Tableのstateの値を指定できます。
例えばソートの場合、特にデフォルト値を設定しない場合のstate.sorting
の値(ソート条件)と対応するURLパラメータは以下です。
state.sorting の値 |
URLパラメータ |
---|---|
[] |
なし |
[{ id: "createdAt", desc: true }] |
?sorting=createdAt.desc |
[{ id: "createdAt", desc: false }] |
?sorting=createdAt.asc |
※createdAt
はカラム名
対して、以下のようにデフォルトのソート順を降順に設定した場合の、
const stateAndOnChanges = useTableSearchParams(router, {
defaultValues: {
sorting: [{ id: "createdAt", desc: true }],
},
});
state.sorting
の値とURLパラメータは以下のようになります。
state.sorting の値 |
URLパラメータ |
---|---|
[] |
?sorting=none |
[{ id: "createdAt", desc: true }] |
なし |
[{ id: "createdAt", desc: false }] |
?sorting=createdAt.asc |
最後に
元々は仕事でTanStack Tableを使っていて、楽にURLパラメータへの同期したかったのがきっかけで作りました。
(仕事でも使い始めていて、サービスのお知らせページでも少し紹介しています)
現状自分が欲しい機能は一通り揃ったので、あとは対応しているTanStack Tableのstateやカスタマイズできる点を増やして、完成度を高めていきたいです。
ちなみにプライベートで自分の作ったpackageをnpmに公開したのが初めてで、npmのpublishの方法やtsupの便利さなどを知れたのもよかったです。
よかったら使ってみてください!(スターもいただけるととても喜びます!)
Discussion