React Table を TypeScript で使う

## Tableでちょっと複雑なことをやりたかった

テーブルの中にボタンを入れた上でソートしたり、チェックボックス付けたり。
![Screen Shot 2022-02-15 at 22.35.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/1e5f4de5-cf1e-cd79-d891-a09552068bf1.png)
こんなようなものを作りたかった。意外と大変だった。

https://react-table.tanstack.com/

今回使ったのは、React Table。
これを Next.js + TypeScript に組み込みたい。

サンプルコードが豊富なので、説明が足らないような気もするが、やりたいことはだいたい何でもできる。Sugeee! が、TypeScript と相性がいまいちよろしくない。map した時に key がないので Warning が出まくる。など、思いの外使いにくかった。

最終的なコードはこちら。
[kurab/next-typescript-react-table](https://github.com/kurab/next-typescript-react-table)

## 準備

```
yarn create next-app next-typescript-react-table --typescript
cd next-typescript-react-table
yarn add react-table
yarn add @types/react-table --dev
```

## 構成の概要

```
├── components
│   ├── primaryButton.tsx
│   ├── SortableTable.tsx
│   └── SortableTableWithRowSelect.tsx
├── pages
│   ├── _app.tsx
│   └── index.tsx
├── types
│   ├── DataType.ts
│   ├── react-table.d.ts
│   └── react.d.ts
└── package.json
```

コードを全部書くと長くなるので、要点だけ。

## TypeScript 対応

まず、react-table を TypeScript で使うには、型定義をする必要がある。これをしないと、TypeScript で React Table は使えない。

```types/react-table.d.ts``` がそれだが、今回面倒だったので、全ての型定義をこちらからコピペした。

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-table

が、使うものだけで良い。今回の場合、Sort と RowSelect 関連のものだけで良い。

Type エラーはまだまだ出るが、全体的な TypeScript 対応はいったんこれだけ。

## Sort 可能な Table

https://github.com/TanStack/react-table/blob/v7/examples/sorting/src/App.js

基本的にはこのサンプル通りで良い。これを component にしたものが、```components/SortableTable.tsx``` だが、サンプルコードのままだと、key がないので、 Warning が出まくる。

key の設定方法は、以下を参考に作った。

https://github.com/TanStack/react-table/discussions/2647#discussioncomment-1026761

```thead``` の一部だけを抜き取ると、

```typescript:sample
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
             ...
```

これがサンプル。key を組み込んだものが

```typescript:SortableTable.tsx
        <thead>
          {headerGroups.map((headerGroup) => {
            const { key, ...restHeaderGroupProps } =
              headerGroup.getHeaderGroupProps();
            return (
              <tr {...restHeaderGroupProps} key={key}>
               ...
```

こんな感じで key を作っていく。```th``` の中に ```span``` があるが、```span``` に ```key``` を与えても Warning は消えないので、```th``` の中身は ```<></>``` で囲う。

## Table の data として string 以外は渡せるのか？

渡せる。

```typescript:index.tsx
  const onClickAlert = (name: string) => {
    alert(name);
  };
  const dataButton: Array<DataButton> = useMemo(
    () => [
      {
        col1: 'Hello',
        col2: <PrimaryButton name={'hello'} callback={onClickAlert} />,
      },
      {...},
    ],
    []
  );
```

こんな感じで、割と何でも渡せる。Type は必要なものを必要なように設定。

## Sort 可能なチェックボックス付きの Table

https://github.com/TanStack/react-table/blob/v7/examples/row-selection/src/App.js

チェックボックス付きはサンプルコードをもとに作る。ソートも付けたいので、html の部分は、前述のコードをそのまま使う。それに加え、今回のサンプルコードで、 ```useTable``` を初期化する際に、```useSortBy``` を付けるだけで良い。


```typescript
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    selectedFlatRows,
    state: { selectedRowIds },
  } = useTable(
    {
      columns,
      data,
    },
    useRowSelect,
    hooks => {
      hooks.visibleColumns.push(columns => [
        {
          id: 'selection',
          Header: ({ getToggleAllRowsSelectedProps }) => (
            <div>
              <IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
            </div>
          ),
          Cell: ({ row }) => (
            <div>
              <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
            </div>
          ),
        },
        ...columns,
      ])
```

サンプルコードでやっているのは、hooks で、チェックボックスをくっつけている。
ここで問題が2つ発生する。

まず、これが動かない。

```typescript
const IndeterminateCheckbox = React.forwardRef(
  ({ indeterminate, ...rest }, ref) => {
    const defaultRef = React.useRef()
    const resolvedRef = ref || defaultRef

    React.useEffect(() => {
      resolvedRef.current.indeterminate = indeterminate
    }, [resolvedRef, indeterminate])

    return (
      <>
        <input type="checkbox" ref={resolvedRef} {...rest} />
      </>
    )
  }
)
```

解決方法は、こちら。

https://stackoverflow.com/questions/65844373/how-to-add-types-to-react-table-indeterminatecheckbox-method


https://github.com/TanStack/react-table/discussions/1989#discussioncomment-1488


賢い人がいるもんだ！stackoverflow の方をそのまま使った。

次に、

```typescript
Cell: ({ row }) => (...)
```

ここでも Type エラーが出る。```Binding element 'any' implicitly has an 'any' type.``` そのままでも使えるが、build はできない。で、

```typescript
Cell: ({ row: any }) => (...)
```

これだと動かない。


https://github.com/microsoft/TypeScript/issues/9657#issuecomment-232254517

こちらを参考に

```typescript
Cell: ({ row }: { row: any }) => (...)
```

とすることで、動くようになる。

あとは、コンポーネント間で select したものをやりとりしたいので、親コンポーネントから子コンポーネントに callback を渡して出来上がり。

## 完成

https://github.com/kurab/next-typescript-react-table

チェック入れた状態でソート順を変更しても、欲しい値はきちんと取れている。Pagenation とかもやりたいところだが、いまいま必要ないので、必要になった時に。

もともとの要件としては、csv をアップロードして、それを sort したりしながら見て、選んだものだけを DB に登録するという内容だったが、そういうのはエクセルで済ませてからアップロードして欲しいものだ。

おわり。