🏓

【React】react-tableを試す

2022/09/28に公開

react-table

TanStack/table: 🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
Reactでtable表示のUIを実装する際に便利な↑こちらのパッケージを試してみたいと思います。
他にも solidvuesvelte 用のパッケージも存在しています。

動作環境

node: v16
react: v18.2.0
react-dom: v18.2.0
@chakra-ui/react: v2.3.1

インストール

$ yarn add @tanstack/react-table

ベーシックな実装

まずは一番シンプルな実装を試してみたいと思います。

import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import React from 'react';

type Book = {
  title: string;
  author: string;
};

const books: Book[] = [
  {
    title: 'ハリー・ポッターと賢者の石',
    author: 'J.K.ローリング',
  },
  {
    title: 'こころ',
    author: '夏目漱石',
  },
];

const columns: ColumnDef<Book, any>[] = [
  {
    accessorKey: 'title',
    header: 'タイトル',
  },
  {
    accessorKey: 'author',
    header: '著者',
  },
];

export const BasicTable: React.FC = () => {
  const table = useReactTable<Book>({
    data: books,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });
  return (
    <div>
      <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>
    </div>
  );
};

↑を実行すると以下のテーブルが表示されます。シンプルすぎで味気ないので次は装飾を追加してみます。

装飾を追加

今回は chakra-ui のTableを使ってみたいと思います。
BasicTable の return を以下に修正します。

import { Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react';
...
export const BasicTable: React.FC = () => {
  const table = useReactTable<Book>({
    data: books,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });
  return (
    <TableContainer>
      <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} borderX="1px solid #e2e8f0">
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </Td>
              ))}
            </Tr>
          ))}
        </Tbody>
      </Table>
    </TableContainer>
  );
};

↑を実行すると以下みたくテーブルっぽくなりました ✨

編集できるようにする

今度はタイトル部分のみ編集できるようにしてみたいと思います。

react-tableではmetaデータを扱える様になっており、自由にカスタム可能なので、
編集された際に呼ばれるCallbackをmetaデータ(TableMeta)内に定義してそちらを呼ぶようしてみたいと思います。まずは TableMeta を拡張する実装を追加します。

import { RowData } from '@tanstack/table-core';

declare module '@tanstack/table-core' {
  interface TableMeta<TData extends RowData> {
    updateData: (rowIndex: number, columnId: string, value: unknown) => void;
  }
}

次に編集部分のコンポーネントを以下内容で作成します。

const defaultColumn: Partial<ColumnDef<Book>> = {
  cell: ({ getValue, row: { index }, column: { id }, table }) => {
    const initialValue = getValue();
    if (id !== 'title') {
      return <>{initialValue}</>;
    }
    // eslint-disable-next-line react-hooks/rules-of-hooks
    const [value, setValue] = useState(initialValue);

    const onBlur = () => {
      table.options.meta?.updateData(index, id, value);
    };

    // eslint-disable-next-line react-hooks/rules-of-hooks
    useEffect(() => {
      setValue(initialValue);
    }, [initialValue]);

    return <Input value={value as string} onChange={(e) => setValue(e.target.value)} onBlur={onBlur} />;
  },
};

最後に useReactTable 部分を以下に修正します。

  const table = useReactTable<Book>({
    data: books,
    columns,
    defaultColumn,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      updateData: (index: number, columnId: string, value: any) => {
        console.log(`table update data index:`, index, 'columnId:', columnId, 'value:', value);
      },
    },
  });

metaデータ内のupdateDataで編集された時のCallbackが受け取れるようになりました。
ここまでで動作させてみると以下の様に、編集できるようになっています。

参考URL

Discussion