公式のサンプルはシンプル
以下のリンクの通り、React Tableとの組み合わせ例をChakra UI公式で掲載してくれている。
しかし、表示するだけのシンプルな機能のため、この機能だけならReact Tableを使う必要性は薄い。
ChakraUI公式のReact Table組み合わせ例
そこで、フィルター・編集機能を追加して実装してみる。
完成系プレビュー
編集可能なテーブルに変えてみる
React Table公式のeditable tableを参考に実装していく。
CompanyInfo型は今回テーブルで扱うレコードの型で勝手につけたもの。
ライブラリの性質上、UIと機能(React Table)は独立しており使い勝手は良い。
// editableにするため
declare module "@tanstack/react-table" {
interface TableMeta<TData extends RowData> {
updateData: (rowIndex: number, columnId: string, value: unknown) => void;
}
}
type CompanyInfo = {
name: string;
field: string;
shareRate: number;
};
type Props = {};
// Give our default column cell renderer editing superpowers!
const defaultColumn: Partial<ColumnDef<CompanyInfo>> = {
cell: ({ getValue, row: { index }, column: { id }, table }) => {
const initialValue = getValue();
// We need to keep and update the state of the cell normally
const [value, setValue] = useState(initialValue);
// When the input is blurred, we'll call our table meta's updateData function
const onBlur = () => {
table.options.meta?.updateData(index, id, value);
};
// If the initialValue is changed external, sync it up with our state
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<input
value={value as string}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
);
}
};
// pagingカスタムフック
const useSkipper = () => {
const shouldSkipRef = useRef(true);
const shouldSkip = shouldSkipRef.current;
// Wrap a function with this to skip a pagination reset temporarily
const skip = useCallback(() => {
shouldSkipRef.current = false;
}, []);
useEffect(() => {
shouldSkipRef.current = true;
});
return [shouldSkip, skip] as const;
};
// フィルターコンポーネント
const Filter = ({
column,
table
}: {
column: Column<any, any>;
table: Table<any>;
}) => {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id);
const columnFilterValue = column.getFilterValue();
return typeof firstValue === "number" ? (
<div>
<Input
type="number"
value={(columnFilterValue as [number, number])?.[0] ?? ""}
onChange={(e) =>
column.setFilterValue((old: [number, number]) => [
e.target.value,
old?.[1]
])
}
placeholder={`Min`}
/>
<Input
type="number"
value={(columnFilterValue as [number, number])?.[1] ?? ""}
onChange={(e) =>
column.setFilterValue((old: [number, number]) => [
old?.[0],
e.target.value
])
}
placeholder={`Max`}
/>
</div>
) : (
<Input
type="text"
value={(columnFilterValue ?? "") as string}
onChange={(e) => column.setFilterValue(e.target.value)}
placeholder={`Search...`}
/>
);
};
Chakra UIがReact Tableから受け取っている部分は以下。
せっかくなのでアイコンをChakra UIのものへ変更している。
/**
* Chakra-UIとreact-tableを用いた編集・フィルター可能テーブル
*/
const SampleTable = (props: Props) => {
const rerender = useReducer(() => ({}), {})[1];
// カラム定義
const columns = useMemo<Array<ColumnDef<CompanyInfo>>>(
() => [
{
header: "半導体関連企業",
footer: (props) => props.column.id,
columns: [
{
accessorKey: "name",
header: () => <span>企業名</span>,
footer: (props) => props.column.id
},
{
accessorKey: "field",
header: () => <span>分野</span>,
footer: (props) => props.column.id
},
{
accessorKey: "shareRate",
header: () => <span>世界シェア</span>,
footer: (props) => props.column.id
}
]
}
],
[]
);
// テーブルに渡すデータ(とりあえず固定値)
const [data, setData] = useState<CompanyInfo[]>([
{ name: "東京エレクトロン", field: "コータ/デベロッパ", shareRate: 90 },
{ name: "信越化学工業", field: "シリコンウエハ", shareRate: 30 }
]);
const [autoResetPageIndex, skipAutoResetPageIndex] = useSkipper();
const table = useReactTable({
data,
columns,
defaultColumn,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
autoResetPageIndex,
// Provide our updateData function to our table meta
meta: {
updateData: (rowIndex, columnId, value) => {
// Skip age index reset until after next rerender
skipAutoResetPageIndex();
setData((old) =>
old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex]!,
[columnId]: value
};
}
return row;
})
);
}
},
debugTable: true
});
return (
<ChakraProvider>
<div>
<div />
<ChakraTable>
<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>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getCanFilter() ? (
<div>
<Filter column={header.column} table={table} />
</div>
) : null}
</div>
)}
</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>
</ChakraTable>
<div />
<div>
<Box>
<IconButton
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
aria-label="jumpFirst"
icon={<ArrowLeftIcon />}
/>
<IconButton
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
aria-label="back1"
icon={<ChevronLeftIcon />}
/>
<IconButton
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
aria-label="next1"
icon={<ChevronRightIcon />}
/>
<IconButton
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
aria-label="jumpFinish"
icon={<ArrowRightIcon />}
/>
</Box>
<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));
}}
>
{[1, 10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
<div>{table.getRowModel().rows.length} Rows</div>
<div>
<button onClick={() => rerender()}>Force Rerender</button>
</div>
</div>
</ChakraProvider>
);
};
感想
React Tableが自身の責務のみ担うスタンスのため、UIライブラリに相性良いと感じた。
余談だが、Chakra UIはProviderで囲ってあげないとデフォルトのスタイルも当たらないようだ。(たしかMUIはthemeを渡さなくてもスタイルが当たっていたはず)
全てのコード
参考
https://chakra-ui.com/getting-started/with-react-table
https://tanstack.com/table/v8/docs/examples/react/editable-data
https://zenn.dev/slowhand/articles/65f1baf9869116
https://qiita.com/kurab/items/93c56277451e96f2b1eb