BLOG

ブログ

Material React Table で高機能なテーブルを作ろう【expand編】

はじめに

今回は前回に引き続き、Material React Tableについて紹介していきます。

タイトルにもある通り今回はexpand編、テーブルの各行を開閉しアコーディオン形式で情報を確認できるようにしたいと思います。

開閉するだけであれば公式のドキュメントで足りてしまうので、行全体をクリック対象にする方法と全体をクリック対象にした上でexpand内に特定のアクションを追加する方法もご紹介します。

あくまで私独自の書き方となりますのでもっとスマートな書き方があるよ!という場合はこっそり教えてください。

なお、今回の記事は前回のMaterial React Table【導入・ローカライズ編】を前提としていますので、事前に確認いただけますと幸いです。

MUIのインストール

今回は前回作成したMaterial React Table のプロジェクトをそのまま使用する前提でお話しを進めていきます。今回はexpand内の要素にMUIのコンポーネントを使用します。

前回はMUI自体をインスールしていませんでしたので 下記のコマンドでMUIを使用できるようにします。

npm install @mui/material @emotion/react @emotion/styled

展開(expand)の設定

それでは早速参りましょう。

テーブルの各列に展開要素を追加する場合は、”renderDetailPanel” を設定する必要があります。

“renderDetailPanel”の値には関数の形でHTML要素もしくは作成したコンポーネント、あるいはMUIのコンポーネントを渡します。試しにMUIのTypographyコンポーネントを渡してみます。

    renderDetailPanel: ()=>(<Typography>Hello</Typography>) 

下記の画面のように展開するボタンが左側に出てこればうまく実装できています。

Material React Table は親切なライブラリなので、デフォルトでは全て開くためのボタンがカラムの左側に出現します。
”renderDetailPanel”内には好きな要素を追加することができ、例にもある通りMUIのコンポーネントも入れることができます。ここではTypographyを渡していますが、もっと複雑なコンポーネント自作したコンポーネントも渡すことが可能です。

また、行を開閉するということは、その行にまつわる何らかの情報を表現したい場合が多いとおもいます。その場合は値の関数部分の引数に”row”を追記することで、その行の情報を取得することができます。引数を取る場合は必ず構造化(オブジェクトの形式)で記述することが重要です。

  renderDetailPanel: ({row})=>(<Typography>row.original.hoge</Typography>) 

コンポーネント内で非同期処理を挟む場合は、Suspence要素を使い、expand内の情報を取得する間、ローディングの表現をさせることもできます。

renderDetailPanel: ({ row }) =>
      ( <Suspense
          fallback={
            <Box sx={{ width: '100%', textAlign: 'center' }}>
              <CircularProgress />
            </Box>
          }
        >
          <AsyncCommponent />
        </Suspense>) ,

展開(expand)ボタンの調整

Material React Table は親切なライブラリなので、デフォルトでは全て開くためのボタンがカラムの左側に出現します。

ですが、さまざまな理由からこのボタンが不要の場合もあるでしょう。そんな時はテーブル定義のオプションに”enableExpandAll”を追加してあげましょう。

const table = useMaterialReactTable({
・・・,
 enableExpandAll: false,
 })

おや・・・?

全て開く用のボタンの代わりとして、カラムの一番左に「展開」という主張の激しいラベルが現れましたね。 斬新なデザインであれば即採用ですが、一般的にこの「展開」は出てきて欲しくない気がします。

そこで”displayColumnDefOptions”を設定します。

const table = useMaterialReactTable({
・・・,
displayColumnDefOptions: {
  'mrt-row-expand': {
    header: '',
  },
 })

“displayColumnDefOptions”はカラムのデフォルト機能を制御するオプションです。

ちなみに上記のコードを簡単に解説すると、”mrt-row-expand”はMaterial React Tableが勝手に追加してくれたヘッダーカラムの名称です。

Material React Tableには自動で生成される要素があるようで、それぞれに名前がついています。 その名前を指定し、さらにheader部分に対し任意の値を指定しているのが上記のコードです。

展開ではなく、「開く」などにもできるということです(しませんけど)。
下記のように綺麗に消えていれば上手く実装できています。

expandボタンの動きを設定する

ボタンの部分は要素を変更をすることができます。

ここでは”muiExpandButtonProps”というオプションを使用します。

デフォルトだと下記の動画のように、矢印の方向が上下になります。今回は閉じてい状態を左向き、開いた状態を下向きになるような、簡単なカスタマイズをします。

”muiExpandButtonProps”は値を関数の形で指定することができます。引数にはテーブル情報とイベント対象の列情報を取ることができます。今回は{row,table}として、テーブル情報にアクセスします。

tableにはメソッドが付いています。ここではsetExpandedというメソッドを使用しています。 端的にいうとexpandを開くかどうかの制御です。

渡す引数によっては全部の行が開いてしまうので、クリックした対象の行だけを開く処理を追記してあげるという認識です。

// expand用のボタンカスタマイズ
    muiExpandButtonProps: ({ row, table }) => ({
      onClick: () => {
        table.setExpanded({ [row.id]: !row.getIsExpanded() })
      },
      sx: {
        transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)',
        transition: 'transform 0.2s',
      },
    }),

行全体をクリック対象にする

一番左のボタンを押して、行の開閉ができるようになりました。ですが、行全体をクリックの対象にしたい場合もあると思います。

行の開閉には”muiTableBodyRowProps“を設定します。
読んで字の如くテーブル内の行情報に関する処理を追加することができます。

関数の形で値を設定します。今回も引数に行の情報(row)とテーブル情報(table)を取ります。
上記で対応したボタンの動き設定と同じく”setExpanded”関数を使用します。

お気づきかもしれませんが、設定している内容は上記とほぼ同じです。Material React Tableは便利なライブラリなので、テーブルの部位ごとにオプションが設定できます。

この法則をある程度理解していれば、設定したいテーブル箇所を公式ドキュメントから見つけ、自由にカスタマイズできるはずです。また、下記のコードの通り、”onClick”や”sx”など、MUIではお馴染みの設定方法が使用できるので、MUIに慣れた方であれば予測的にコーディングすることができます。

この互換性、素晴らしすぎます。

// 行のクリックイベント
    muiTableBodyRowProps: ({ row, table }) => ({
      onClick: () => {
        table.setExpanded({ [row.id]: !row.getIsExpanded() })
      },
      sx: {
        cursor: 'pointer',
      },
    }),

行全体をクリック範囲にしたexpand内で特定のアクションを行う

では、今回のメインディッシュ、行全体をクリック範囲にしたexpand内で特定のアクションを行う実装をしてみましょう。

せっかくexpandで行を広げたからには、詳細を表示するだけでなく、特定のアクションに結びつけたくなるのが人の性です。

まずは”renderDetailPanel”の部分を下記のように書き換え、動作確認用のボタンを用意します。この状態で動作を確認してみます。

renderDetailPanel: ()=>(
    <Button variant='contained' 
     onClick={()=>console.log("test")}>
     Hello
    </Button>) 

関数は正常に動作していますが、ボタンクリックと同時に行が閉じてしまいますね。

それもそのはず。先ほど設定した行全体をクリック範囲にする設定は、expand内のボタンよりも上位の要素です。つまりボタン要素と行要素、2つのクリックイベントが実行されている状態になります。

どうすれば解決できるのか。

まず一番最初に思いつくのは、行全体のクリックを止めることです。クリック対象を矢印のみにすれば、expand内でイベント要素が重なることなく、ボタンのイベントのみを実行できます。

renderDetailPanel: ()=>(
    <Button variant='contained' 
     onClick={()=>console.log("test")}>
     Hello
    </Button>) 
   // muiTableBodyRowProps: ({ row, table }) => ({
    //   onClick: () => {
    //     table.setExpanded({ [row.id]: !row.getIsExpanded() })
    //   },
    //   sx: {
    //     cursor: 'pointer',
    //   },
    // }),

ですが諦めてはいけません。両方実現したい私は以下のような方法で実装してみました。

 renderDetailPanel: ()=>(<Button variant='contained' onClick={
      (e)=>
     { e.stopPropagation() 
      console.log("test")}}>Hello</Button>) ,

ボタンのイベントのみが実行され、行の開閉イベントが実装されませんでした。

簡単に解説すると、ボタンクリック時のイベントに対して、ボタンより上位階層の要素が持つイベントを中止するための関数”stopPropagation”を使用しています。

このやり方は上位要素のイベントを阻害してしまうため、思わぬバグを引き起こすかもしれません。また、expand内で行いたいイベント全てにこの方式を組み込むのは結構手間がかかります。

ですので、行全体のクリックを諦めるか、上記の方法で実装するかは、要件などさまざまなことを踏まえた上で決断されることをお勧めします。

ですが一応エラーなく、行全体のクリックとexpand内のクリックイベントを分けて実行することができました。うれしい。

まとめ

今回はexpand編ということで、前回に引き続きMaterial React Tableの解説をしました。

他にも行を入れ替えるなど便利な機能が盛りだくさんのライブラリなので、これから人気爆発するんじゃないかなと個人的に思っています。

以上、遠藤でした!

それでは〜!