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

はじめに

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

みなさん、React 使ってますかー? そして Material-UI 使ってますかー?

今回は、Material-UI が用意している material-table というデータテーブルをカスタマイズして、日付範囲の指定によるフィルタリングの実装方法についてご紹介していきたいと思います。

実際に動かしてみたい方は、下記のリポジトリをご参照ください。

GitHub - iam326/material-table-filter-by-date-range

material-table とは

Material-UI が用意しているデータテーブルのコンポーネントです。テーブル内のデータについて、フィルタリングやソート、検索、編集、CSVやPDFへのエクスポートなどを簡単に実装することができます。また、元々用意されている機能を独自にカスタマイズすることも可能です。

material-table のドキュメントに、各機能を使ったデモが用意されているので、どんな感じのコンポーネントなのかを事前に体感することができます。

標準で用意されているフィルター

カスタマイズを行う前に、material-table が標準で用意しているフィルターについてご紹介します。

MaterialTable コンポーネントの options に filtering: true を渡してあげると、全ての列にフィルターが設置されます。デフォルトでは文字列の部分一致によるフィルターになります。

import React from 'react';
import MaterialTable from 'material-table';

const tableData = [
  {
    couponId: '0001',
    storeName: '秋葉原店',
    couponName: '日用品 お得クーポン',
    discountRate: 10,
    startDate: '2020-08-01',
    endDate: '2020-08-07',
  },
  ...
];

const DataTable: React.FC = () => {
  return (
    <MaterialTable
      options={{
        search: false,
        draggable: false,
        filtering: true, // これでフィルターが設置される ★
      }}
      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="クーポン一覧"
    />
  );
};

export default DataTable;

UI は下記のようになります。横線が3本引かれているアイコンの部分がフィルターになります。

クーポン名のフィルターに「日用品」と入力してみます。すると、下記のようにフィルタリングが適用されて、クーポン名に「日用品」を含むデータだけが表示されるようになります。

列の設定を行う columns に、lookup を指定することで、チェックボックス形式のフィルターを設置することができます。

columns={[

...

  {
    title: '店舗',
    field: 'storeName',
    lookup: { 秋葉原店: '秋葉原店', 浅草店: '浅草店' }, // これを追加する ★
  },

UI は下記のようになります。フィルターの部分をクリックすると、チェックボックスが表示されます。

試しに店舗名が「秋葉原店」のデータをフィルタリングしてみます。先ほど指定したクーポン名のフィルタリングと合わせて、どちらの条件にもマッチするデータだけが表示されるようになります。

フィルターをカスタマイズする

material-table では、先ほど見たように文字列の部分一致によるフィルターと、チェックボックス形式のフィルターの2種類を標準で用意してくれています。ここからは独自でカスタマイズしたフィルターを設置する方法についてご紹介していきたいと思います。

まず、フィルターとして埋め込むコンポーネントを実装します。今回は、日付範囲の指定によるフィルタリングを行いたいので、Date Picker を埋め込みます。自分で Date Picker を実装するのは大変なので、これまた Material-UI が用意している Date Picker を使用します。

props に含まれる columnDefonFilterChanged は material-table の仕様として渡されるものです。Date Picker の onChange 内で、onFilterChanged に選択した日付を渡しています。そうすることで、material-table 側にフィルターの値が変わったことを知らせることができます。

const FilterDatePicker: React.FC<Props> = (props) => {
  const { columnDef, onFilterChanged, label } = props;
  const classes = useStyles();

  return (
    <KeyboardDatePicker
      className={classes.date}
      disableToolbar
      id={`date-picker-${columnDef.tableData.id}`}
      variant="inline"
      inputVariant="standard"
      format="yyyy-MM-dd"
      margin="normal"
      error={false}
      helperText={null}
      value={columnDef.tableData.filterValue || null}

      // 選択した日付を新たなフィルターの値として、material-table 側に渡してあげる ★
      onChange={(_, inputValue) => {
        onFilterChanged(columnDef.tableData.id, inputValue);
      }}
 
      label={label ? label : ''}
    />
  );
};

次に、上記の Date Picker を 実際に material-table のフィルターとして埋め込んでいきます。material-table の列設定を行う columns に、filterComponent というプロパティがあるので、そこに上記で実装した Date Picker を渡してあげます。

また、フィルターの条件を customFilterAndSearch というプロパティで実装します。先ほど、Date Picker 側で onFilterChanged に選択した日付を渡していましたが、この値が customFilterAndSearch の第1引数になります。フィルターの値が変わると、customFilterAndSearch はテーブルの行数分だけ呼び出され、第2引数に1行分のデータが渡されるので、この2つの引数を使ってフィルターの条件を組んであげます。

columns={[

...

  {
    title: '利用開始日',
    field: 'startDate',
    cellStyle: { textAlign: 'right' },

    // ここに Date Picker を埋め込む ★
    filterComponent: (props) => (
      <FilterDatePicker
        columnDef={props.columnDef}
        onFilterChanged={props.onFilterChanged}
        label={'から'}
      />
    ),
    
    // 指定した日付以降のデータを表示するための条件を組む ☆
    customFilterAndSearch: (
      filterValue: string | null,
      rowData: { startDate: string }
    ) => !filterValue || rowData.startDate >= filterValue,
  },
  {
    title: '利用終了日',
    field: 'endDate',
    cellStyle: { textAlign: 'right' },
    
    // こちらにも Date Picker を埋め込む ★
    filterComponent: (props) => (
      <FilterDatePicker
        columnDef={props.columnDef}
        onFilterChanged={props.onFilterChanged}
        label={'まで'}
      />
    ),
    
    // 指定した日付以前のデータを表示するための条件を組む ☆
    customFilterAndSearch: ( 
      filterValue: string | null,
      rowData: { endDate: string }
    ) => !filterValue || rowData.endDate <= filterValue,
  },

UI は下記のようになります。利用開始日と利用終了日に Date Picker を埋め込むことができました。

下記のように日付を選択してみます。2020年8月中に利用が開始 & 終了するクーポンのデータが表示されます。

下記のページに、デモ用の gif を貼っていますので、動いている絵を見たい方はご参照ください。

GitHub - iam326/material-table-filter-by-date-range

おわりに

今回は、material-table のフィルターをカスタマイズして、日付範囲の指定によるフィルタリングを実装してみましたが、いかがだったでしょうか。

データを一覧表示するようなページを実装する際、material-table のようなコンポーネントが用意されていると、実用性があってとても便利ですよね。その反面、カスタマイズが必要になった時、勝手が効かず困ったりすることもありますが、material-table はその辺がよく考えられているように思えます。

公式のドキュメントを見るだけでは読み取れませんが、GitHub の Issue でやりたいことを検索すると、答えが書いてあったりするので、興味を持った方は色々と調べてみてください。私の方でもカスタマイズのネタがあれば、記事にしていきたいと思います。

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