material-tableのセル内でデータごとの値を使用したURLリンクをレンダリングしたい

2020.08.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部の若槻です。

今回は、前回の記事で作成したReact + Material-UI + material-tableのアプリで、テーブルのセル内でデータごとの値を使用したURLリンクをレンダリングしてみました。

実現したいこと

各商品データごとに予約数1以上ならその商品に対する予約一覧ページへのURLリンクを予約数列のセル内でレンダリングしたいです。

例えば、商品IDA0001の「ペンライトセット」なら/reserve/productId=A0001へのリンク、商品IDC0001の「タオル」なら/reserve/productId=C0001へのリンクを貼り、予約なしの「パンフレット」ならリンク無し、のようにしたいです。 スクリーンショット_2020-08-18_0_16_29.png

やってみる

src/components/pages/ProductPage.tsxのコードを次のように変更します。

ハイライトされた行が前回との変更(行追加)部分となります。それぞれ解説していきます。

import React from 'react';
import MaterialTable from 'material-table';
import GenericTemplate from '../templates/GenericTemplate';
import { ArrowUpward } from '@material-ui/icons';
import * as colors from '@material-ui/core/colors';
import SelectList from '../atoms/SelectList';
import moment from 'moment';
import { Link } from 'react-router-dom';

const ProductPage: React.FC = () => {
  interface Product {
    itemName: string;
    price: number;
    reserveDuedate: string;
    reserveCount: number;
    productId: string;
  }
  return (
    <GenericTemplate title={'商品ページ'}>
      <MaterialTable
        icons={{
          SortArrow: React.forwardRef((props, ref) => (
            <ArrowUpward
              {...props}
              ref={ref}
              style={{ color: colors.blue[800] }}
            />
          )),
        }}
        columns={[
          {
            title: '商品名',
            field: 'itemName',
            defaultSort: 'asc',
            filtering: false,
          },
          {
            title: '予約期限',
            field: 'reserveDuedate',
            filterComponent: (props) => (
              <SelectList
                columnDef={props.columnDef}
                onFilterChanged={props.onFilterChanged}
                items={[
                  ['all', 'すべて'],
                  ['notOverDue', '未超過'],
                  ['overDue', '超過'],
                ]}
              />
            ),
            customFilterAndSearch: (filterValue: string, rowData: Product) => {
              const jstNow = new Date().toLocaleString('ja', {});
              if (filterValue === 'notOverDue') {
                return moment(rowData.reserveDuedate).isSameOrAfter(jstNow);
              } else if (filterValue === 'overDue') {
                return moment(rowData.reserveDuedate).isBefore(jstNow);
              }
              return true;
            },
          },
          {
            title: '価格',
            field: 'price',
            type: 'numeric',
            filtering: false,
          },
          {
            title: '予約数',
            field: 'reserveCount',
            type: 'numeric',
            filterCellStyle: { textAlign: 'right' },
            filterComponent: (props) => (
              <SelectList
                columnDef={props.columnDef}
                onFilterChanged={props.onFilterChanged}
                items={[
                  ['all', 'すべて'],
                  ['reserved', '予約あり'],
                  ['notReserved', '予約なし'],
                ]}
              />
            ),
            customFilterAndSearch: (filterValue: string, rowData: Product) => {
              if (filterValue === 'reserved') {
                return rowData.reserveCount > 0;
              } else if (filterValue === 'notReserved') {
                return rowData.reserveCount === 0;
              }
              return true;
            },
            render: (rowData: Product) => {
              if (rowData.reserveCount) {
                return (
                  <Link
                    to={{
                      pathname: '/reserve',
                      search: `?productId=${rowData.productId}`,
                    }}
                  >
                    {rowData.reserveCount}
                  </Link>
                );
              } else {
                return rowData.reserveCount;
              }
            },
          },
        ]}
        data={[
          {
            itemName: 'ペンライトセット',
            price: 20000,
            reserveDuedate: '2020/08/10',
            reserveCount: 20,
            productId: 'A0001',
          },
          {
            itemName: 'パンフレット',
            price: 4000,
            reserveDuedate: '2020/09/15',
            reserveCount: 0,
            productId: 'B0001',
          },
          {
            itemName: 'タオル',
            price: 3000,
            reserveDuedate: '2020/08/30',
            reserveCount: 5,
            productId: 'C0001',
          },
          {
            itemName: 'Tシャツ',
            price: 4500,
            reserveDuedate: '2020/08/30',
            reserveCount: 10,
            productId: 'C0002',
          },
        ]}
        options={{
          showTitle: false,
          filtering: true,
        }}
      />
    </GenericTemplate>
  );
};

export default ProductPage;

まず、Reactでは<Link>というコンポーネントを使ってサイト内リンクへのナビゲーションを実装します。

<Link>は次のようにしてインポートすれば使用できます。

import { Link } from 'react-router-dom';

そして、material-tableで列のレンダリングを任意の内容でオーバーライドしたい場合は、columnsrenderプロパティを使います。またrenderにfunctionを指定すると第一引数にデータが渡されるので、次のように予約数(reserveCount)に応じてレンダリング内容を変更できます。そして予約数が1以上の場合に<Link>コンポーネントによりURL/reserve/productId={productId}へのリンクをセル内でレンダリングするようにしています。

            render: (rowData: Product) => {
              if (rowData.reserveCount) {
                return (
                  <Link
                    to={{
                      pathname: '/reserve',
                      search: `?productId=${rowData.productId}`,
                    }}
                  >
                    {rowData.reserveCount}
                  </Link>
                );
              } else {
                return rowData.reserveCount;
              }
            },

予約数列に各商品ごとの予約へのリンクを貼ることができました。 スクリーンショット_2020-08-18_1_08_53.png

リンクをクリックすると/reserve/productId={productId}へちゃんと飛べました。(/reserveページは未実装なので画面には何も表示されていません。) スクリーンショット_2020-08-18_1_11_37.png

外部へのリンクの場合

<Link>コンポーネントはサイト内へのナビゲーションリンクに使用します。外部へのリンクを貼りたい場合は次のように<a>タグを使えばOKです。

            render: () => {
              return <a href="https://classmethod.jp/">クラスメソッド</a>;
            },

image.png

リンクをクリックすると外部のページが開けました。 スクリーンショット_2020-08-18_1_20_26.png

おわりに

React + Material-UI + material-tableのアプリで、テーブルのセル内でデータごとの値を使用したURLリンクをレンダリングしてみました。

本来は商品詳細ページを設けてそのページ内で予約一覧へのリンクを提供するべきかも知れませんが、今回は商品一覧ページのみで実現する方法を考えてみました。

参考

以上