【React】material-react-table v2のフィルター作成方法について

おはこんばんにちは、フリーランスエンジニアの[せな](https://senablog.com/)です。
最近ReactでWebアプリを開発する際に[Material React Table](https://www.material-react-table.com/)のフィルターを作成する方法が分からなかったので備忘録も兼ねて紹介したいと思います。

プロジェクトのソースコードは[GitHub](https://github.com/tsukiyama85/material-react-table-customfilter)にあげています。
詳細が気になる方はそちらも参照してください。


## 事前準備
今回はYarnを使ってプロジェクトを作成します。
その他の方法でプロジェクトを作成する場合は適宜読み替えてください。
```bash
cd /path/to/your/project
yarn create react-app app --template typescript
cd app
```

```
yarn add material-react-table @mui/material @mui/x-date-pickers @mui/icons-material @emotion/react @emotion/styled dayjs
```

```
# ↓はフィルターとは関係ありません。
# プロジェクト作成時に必要だった最低限のライブラリです。
yarn add react-router-dom tailwindcss
```

## ディレクトリ構成
今回は以下のディレクトリ構成で作成します。
今回のフィルター説明とは直接関係はないので、読み飛ばしても大丈夫です。
```
app
├── src
│   ├── App.tsx - メインコンポーネント
│   ├── providers
│   │   ├── app.tsx - プロバイダー
│   ├── routes
│   │   ├── index.tsx - ルーティング
│   ├── features
│   │   ├── table
│   │   │   ├── components
│   │   │   │   ├── UserTable.tsx - テーブルコンポーネント
│   │   │   │   ├── CustomDatePicker.tsx - 日付ピッカーコンポーネント
│   │   │   ├── filters
│   │   │   │   ├── dateFilter.ts - 日付フィルター（今回の本題）
│   │   │   ├── routes
│   │   │   │   ├── index.ts - ルーティング
│   │   │   │   ├── TableRoute.tsx - テーブルのルーティング
```

## フィルターの作成方法
ここからが本題です。
フィルターを作成するためには、`MRT_FilterFn`を使用します。

以下は、日付の範囲を指定するフィルターの例です。
```tsx
import { MRT_FilterFn } from "material-react-table";

const dateFilter: MRT_FilterFn<any> = (
  row,
  columnId,
  filterValue,
) => {
  const rowValueDate: Date = row.getValue(columnId);

  const filterStartDate = filterValue[0];
  const filterEndDate = filterValue[2];

  if (filterStartDate && filterEndDate) {
    // 期間の開始・終了が指定されている場合
    const isValid = rowValueDate >= filterStartDate && rowValueDate <= filterEndDate;
    return isValid;
  } else if (filterStartDate && !filterEndDate) {
    // 期間の開始が指定されている場合
    const isValid = rowValueDate >= filterStartDate;
    return isValid;
  } else if (!filterStartDate && filterEndDate) {
    // 期間の終了が指定されている場合
    const isValid = rowValueDate <= filterEndDate;
    return isValid;
  } else {
    // 期間が指定されていない場合
    return true;
  }
}

export default dateFilter;
```

このフィルターを使用するためには、`useMaterialReactTable`の`filterFns`に登録する必要があります。
以下は、`useMaterialReactTable`のサンプルコードです。

```tsx
const table = useMaterialReactTable({
    columns,
    data,
    initialState: {
      showColumnFilters: true,
      showGlobalFilter: true,
    },
    enableColumnResizing: true, // 列幅変更
    enableColumnFilters: true, // 列フィルター
    enableRowSelection: false, // 行のチェックボックス
    enableFilterMatchHighlighting: false, // フィルターのハイライト
    enableColumnFilterModes: true, // 列フィルターモード
    enableColumnDragging: false, // 列ドラッグ
    enableColumnOrdering: false, // 列並び替え
    enableColumnPinning: true, // 列固定
    enableColumnActions: false, // 列アクション
    filterFns: {
      // 日付のフィルター処理
      'dateFilter': dateFilter as FilterFn<any>,
    },
    state: {
      columnFilters,
      globalFilter,
      isLoading
    },
    onColumnFiltersChange: setColumnFilters,
    onGlobalFilterChange: setGlobalFilter,
});
```

そして、`fileterFns`に登録した`dateFilter`を使用するカラムの`filterFn`に指定します。
```tsx
{
    accessorKey: 'birthday',
    header: '誕生日',
    minSize: 75,
    maxSize: 400,
    size: 125,
    enableSorting: false, // ソートを無効
    enableColumnActions: false, // : のカラムアクションを無効
    enableColumnFilter: false, // カラムのフィルター表示を無効
    filterFn: 'dateFilter',
},
```

これで、誕生日の範囲を指定してフィルターを行うことができるようになっているかと思います。

以下のような、日付を選択するコンポーネントを作成して、フィルターを行うことができます。
```tsx
  const header = table.getHeaderGroups()[0]
  const birthdayColumn = header.headers[2].column  // 誕生日カラム

  const [startDateFilterValue, setStartDateFilterValue] = useState<string | null>(dayjs().year(2024).month(0).date(1).format("YYYY/MM/DD"));
  const [endDateFilterValue, setEndDateFilterValue] = useState<string | null>(dayjs().year(2024).month(1).date(-1).format("YYYY/MM/DD"));

  return (
    <div className="flex flex-col justify-center items-center w-full min-h-screen">
      <CustomDatePicker
        value={dayjs(startDateFilterValue).format("YYYY-MM-DD") ?? ''}
        onChange={(value: string) => {
          birthdayColumn.setFilterValue((old: [string, string, string, string]) => [value, old?.[1], old?.[2], old?.[3]])
          setStartDateFilterValue(value);
        }}
        label="開始日"
        className="w-40"
      />
      <CustomDatePicker
        value={dayjs(endDateFilterValue).format("YYYY-MM-DD") ?? ''}
        onChange={(value: string) => {
          birthdayColumn.setFilterValue((old: [string, string, string, string]) => [old?.[0], old?.[1], value, old?.[3]])
          setEndDateFilterValue(value);
        }}
        label="終了日"
        className="w-40"
      />
      <div className="w-5/6">
        <MaterialReactTable table={table} />
      </div>
    </div>
  );
```

実際に動作させると次のようなイメージとなります。
![datefilter-describe.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3692225/3bb4df0b-7ce6-3ce6-36ab-3ee0cd7a7958.gif)


以上で、MRTのテーブルにフィルターを作成する方法を紹介しました。

## まとめ
今回説明した方法は日付をフィルターする方法ですが、他のフィルターも同様の方法で作成することができます。
例えば日時(datetime)のフィルターについては以下にサンプルコードをあげていますので、参考にしてみて下さい。
[datetimeFilter.ts](https://github.com/tsukiyama85/material-react-table-customfilter/blob/main/app/src/features/table/filters/datetimeFilter.ts)

他にもっと良い方法があるよ！ って方がいればコメントで教えてもらえると嬉しいです！

ではでは〜

## 参考
- [Column Filtering Feature Guide](https://www.material-react-table.com/docs/guides/column-filtering)
- [Tanstack Table v8 React Example: Filters](https://tanstack.com/table/v8/docs/framework/react/examples/filters)