material-table でコンポーネント再描画時にフィルターがリセットされてしまう現象を対処する

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

Material-UI のテーブルを基に作られた material-table のフィルターですが、コンポーネントの再描画が走ると、フィルターが勝手にリセットされてしまう現象が発生しまして、それを対処したので記事にしようと思います。

material-table のフィルターとはなんぞやって思った方には、以前ブログに書いた下記の記事を読んで頂けると、概要が分かるかと思います。

material-table で日付範囲の指定によるフィルタリングがしたい

また、対処後のソースコードを下記のリポジトリに置いていますので、参考にして頂ければと思います。

GitHub - iam326/material-table-filter-reset-when-re-rendering-component

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.6
BuildVersion:   19G2021

$ node --version
v12.18.2

$ yarn --version
1.22.4

$ yarn list --depth=0
...
├─ @material-ui/core@4.11.0
├─ material-table@1.68.0
├─ react@16.13.1
├─ typescript@3.7.5
...

現象

例えば、material-table で作ったクーポン一覧テーブルがあるとします。行をクリックしてクーポンを選択すると、背景の色が変わり、クーポンを使用するためのボタンが押せるようになる感じの UI を想定します。

ソースコードを下記に記載します。

行をクリックした際の処理は、onRowClick に記述します。今回は行をクリックした際、どのクーポンを選んだかを記録しておくために、selectedCouponId という state を更新する処理を実装しています。

また、options に filtering: true を渡して、全ての列に文字列の部分一致によるフィルターを設置します。

const DataTable: React.FC = () => {
  const [selectedCouponId, setSelectedCouponId] = useState<string | null>(null);
  return (
    <MaterialTable
      options={{
        search: false,
        draggable: false,
        filtering: true,  // ★ 全ての列にフィルターを設置
        rowStyle: (rowData) => ({
          backgroundColor: selectedCouponId === rowData.couponId ? '#eee' : '',
        }),
      }}
      onRowClick={(_, rowData) => // ★ 行クリック時の処理
        setSelectedCouponId(
          rowData &&
            (!selectedCouponId || selectedCouponId !== rowData.couponId)
            ? rowData.couponId
            : null
        )
      }
      actions={[
        {
          icon: () => (
            <Button
              variant="contained"
              color="primary"
              disabled={!selectedCouponId}
            >
              使用する
            </Button>
          ),
          disabled: !selectedCouponId,
          isFreeAction: true,
          onClick: () => alert('Button Click!!'),
        },
      ]}
      columns={[
        {
          title: 'クーポン ID',
          field: 'couponId',
        },
        { title: '店舗', field: 'storeName' },
        {
          title: 'クーポン名',
          field: 'couponName',
        },
        {
          title: '割引率',
          field: 'discountRate',
          cellStyle: { textAlign: 'right' },
        },
        {
          title: '利用開始日',
          field: 'startDate',
          cellStyle: { textAlign: 'right' },
        },
        {
          title: '利用終了日',
          field: 'endDate',
          cellStyle: { textAlign: 'right' },
        },
      ]}
      data={tableData}
      title="クーポン一覧"
    />
  );
};

では、このテーブルに対して、店舗名に「秋葉原」を含むクーポンのみ表示させるよう、フィルタリングを適用してあげます。

その後、行をクリックしてクーポンを選択すると、フィルターが勝手にリセットされ、全てのクーポンが表示されてしまいます。行をクリックしたことで selectedCouponId が更新され、コンポーネントの再描画が走り、列情報がリセットされた結果、この現象が起きてしまうということが考えられます。

対処法

簡単です。列情報自体を state 化するだけで、この現象を対処できます。これでコンポーネントの再描画が走っても列情報を維持することができます。

const [columnInfo] = useState<Array<Column<Coupon>>>([ // ★ 列情報を state 化
  {
    title: 'クーポン ID',
    field: 'couponId',
  },
  ...
    
<MaterialTable
  ...
  columns={columnInfo} // ★ state 化した列情報を渡す
  data={tableData}
  title="クーポン一覧"
/>

フィルタリング適用後に、行をクリックしてクーポンを選択しても、フィルターはそのまんまになりました!

おわりに

今回は、material-table でコンポーネント再描画時にフィルターがリセットされてしまう現象の対処方法についてご紹介しました。

material-table が標準で用意しているフィルターと、行クリック時の処理を単純に組み合わせた結果、この現象が発生してしまいました。最初はどう対処すれば良いのか見当がつかなかったのですが、GitHub の Issue を探っていたら、同じような現象で困っている人がいて、その中のコメントで答えを見つけることができました。めちゃ助かった。

Issues - Filtering states reset upon rerendering remote table

今回は以上になります。最後まで読んで頂きありがとうございました!