ReactでMUIのテーブルを操作

React_icon React
この記事は約17分で読めます。

この記事は以下の方を対象に書いています。

  • MUIのテーブルの使い方を知りたい
  • MUIのテーブル操作をするサンプルコードを見たい

準備

まずは、Reactのプロジェクトを作り、MUIのインストールをしましょう。

開発環境は以下の通りになります。

  • react: 18.2.0
  • typescript: 4.9.5
  • mui: 5.14.2

実装開始

準備が完了したら早速実装に入ります。

行の内容を定義

まずはテーブルを構成する行の定義をします。

今回は、以下のように4つの項目を持つ行を定義します。

type Row = {
  no: number
  name: string
  age: number
  note: string
};

実データをState管理

次に実際に扱うデータを作成し、State管理します。

まずは、実データの作成からです。

  const initData: Row[] = [
    { no: 1, name: 'name1', age: 43, note: '野球が好き' },
    { no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
    { no: 3, name: 'name3', age: 25, note: '相模原が好き' },
    { no: 4, name: 'name4', age: 78, note: '相撲が好き' },
  ];

次は、作成したinitDataを初期値に持つStateを作成します。

const [rows, setRows] = useState<Row[]>(initData);

テーブルの描画

最後にテーブルを描画すれば完了になります。

  return (
    <>
      <Table sx={{ maxWidth: 550 }} aria-label="simple table">
        {/* ヘッダー */}
        <TableHead >
          <TableRow sx={{ backgroundColor: 'darkGray' }}>
            <TableCell align="center">番号</TableCell>
            <TableCell align="center">名前</TableCell>
            <TableCell align="center">年齢</TableCell>
            <TableCell align="left">備考</TableCell>
          </TableRow>
        </TableHead>
        {/* ボディ */}
        <TableBody>
          {/* rows(行が格納された配列をループ処理) */}
          {rows.map((row, index) => {
            return (
              <TableRow
                key={`index-${row.no}`}
              >
                <TableCell align="center">{row.no}</TableCell>
                <TableCell align="center">{row.name}</TableCell>
                <TableCell align="center">{row.age}</TableCell>
                <TableCell align="left">{row.note}</TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </>
  );

ここでのポイントは、16行目から27行目までの処理になります。

ここでは、State管理しているrowsを要素数分ループするようにしています。

ここで便利なのがmapメソッドです。

16行目で(row, index)と記述していますが、ここの文字はなんでもOKです。

ただ、できるだけ分かりやすい名前にすると良いです。

ここでのrowは、ループ中の要素(1行ずつ)を表しています。

よって、row.nameなどRowの中の要素を取り出すことが可能です。

次にindexですが、これにはループ中の番号が0から順番に入ります。

今回は要素数が4なので0~3までの数字が入っています。

ちなみに、このindexは書かなくても問題ありません。

ただ、あると便利なときは多いです!!

全体像

最後に今まで解説をしたコードを載せておきますので、

実行してみて、挙動を確かめてみてください。

今回はすべてを1つのファイル(App.tsx)に記述していますが、

慣れている方は、もっと細かくコンポーネント化してみると良いと思います。

import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import React, { useRef, useState } from 'react';

// 行の内容
type Row = {
  no: number
  name: string
  age: number
  note: string
};

function App(): JSX.Element {
  // stateに入れる初期データ
  const initData: Row[] = [
    { no: 1, name: 'name1', age: 43, note: '野球が好き' },
    { no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
    { no: 3, name: 'name3', age: 25, note: '相模原が好き' },
    { no: 4, name: 'name4', age: 78, note: '相撲が好き' },
  ];

  // useStateの宣言と初期値の格納
  const [rows, setRows] = useState<Row[]>(initData);

  return (
    <>
      <Table sx={{ maxWidth: 550 }} aria-label="simple table">
        {/* ヘッダー */}
        <TableHead >
          <TableRow sx={{ backgroundColor: 'darkGray' }}>
            <TableCell align="center">番号</TableCell>
            <TableCell align="center">名前</TableCell>
            <TableCell align="center">年齢</TableCell>
            <TableCell align="left">備考</TableCell>
          </TableRow>
        </TableHead>
        {/* ボディ */}
        <TableBody>
          {/* rows(行が格納された配列をループ処理) */}
          {rows.map((row, index) => {
            return (
              <TableRow
                key={`index-${row.no}`}
              >
                <TableCell align="center">{row.no}</TableCell>
                <TableCell align="center">{row.name}</TableCell>
                <TableCell align="center">{row.age}</TableCell>
                <TableCell align="left">{row.note}</TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </>
  );
};

export default App;

カスタマイズ(行にボタンを追加)

今回は、もう少しカスタマイズしてみます。

行の中にボタンを追加してみようと思います。

JSXの拡張

まずは以下のようにJSXを拡張して最後の列にボタンを追加します。

これだけで、見た目上は完成になります。

ただ、機能もつけたいので、次で削除用の関数を追記していきます。

return (
    <>
      <Table sx={{ maxWidth: 550 }} aria-label="simple table">
        {/* ヘッダー */}
        <TableHead >
          <TableRow sx={{ backgroundColor: 'darkGray' }}>
            <TableCell align="center">番号</TableCell>
            <TableCell align="center">名前</TableCell>
            <TableCell align="center">年齢</TableCell>
            <TableCell align="center">備考</TableCell>
            <TableCell align="center">アクション</TableCell> {/* 追記 */}
          </TableRow>
        </TableHead>
        {/* ボディ */}
        <TableBody>
          {/* rows(行が格納された配列をループ処理) */}
          {rows.map((row, index) => {
            return (
              <TableRow
                key={`index-${row.no}`}
              >
                <TableCell align="center">{row.no}</TableCell>
                <TableCell align="center">{row.name}</TableCell>
                <TableCell align="center">{row.age}</TableCell>
                <TableCell align="center">{row.note}</TableCell>
                <TableCell align="center">
                  <Button onClick={() => deleteTarget(index)}>削除</Button> {/* 追記 */}
                </TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </>
  );

削除用の関数を定義

次は、削除ボタンを押したときに実行される関数を記述します。

  const deleteTarget = (index: number): void => {
    const tmpRow = Array.from(rows);
    tmpRow.splice(index, 1);
    setRows(tmpRow);
  };

これが、意外とハマるポイントです。

2行目で、わざわざfrom関数を使用しているのには理由があります。

普通なら対象行を削除したrowsをそのままStateに格納すればうまくいきそうですよね?

でも、Reactではうまくいきません。

その理由はuseStateでは、参照が同じだと状態に変更があったと検知されないためです。

そのため、from関数を使用して値のみのコピーをして、別の参照としてsetしています。

このようにすることで、状態に変更があったと検知され、再描画が行われます。

※値のみコピーすることを「浅いコピー」や「シャローコピー」と呼びます。

※このようなシャローコピーなど便利な操作をサポートしてくれるライブラリも存在します。

※気になる方は「lodash」と検索してみてください。

全体像(ver.カスタマイズ)

カスタマイズしたコードの全体像も貼っておきます。

import { Button, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import React, { useRef, useState } from 'react';
import { JsxElement } from 'typescript';

// 行の内容
type Row = {
  no: number
  name: string
  age: number
  note: string
};

function App(): JSX.Element {
  // stateに入れる初期データ
  const initData: Row[] = [
    { no: 1, name: 'name1', age: 43, note: '野球が好き' },
    { no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
    { no: 3, name: 'name3', age: 25, note: '相模原が好き' },
    { no: 4, name: 'name4', age: 78, note: '相撲が好き' },
  ];

  // useStateの宣言と初期値の格納
  const [rows, setRows] = useState<Row[]>(initData);

  // 対象行を削除する関数
  const deleteTarget = (index: number): void => {
    // シャローコピー
    const tmpRow = Array.from(rows);
    // コピーしたデータの対象行(対象行は引数のindexで特定可能)を削除
    tmpRow.splice(index, 1);
    // データのセット
    setRows(tmpRow);
  };

  return (
    <>
      <Table sx={{ maxWidth: 550 }} aria-label="simple table">
        {/* ヘッダー */}
        <TableHead >
          <TableRow sx={{ backgroundColor: 'darkGray' }}>
            <TableCell align="center">番号</TableCell>
            <TableCell align="center">名前</TableCell>
            <TableCell align="center">年齢</TableCell>
            <TableCell align="center">備考</TableCell>
            <TableCell align="center">アクション</TableCell>
          </TableRow>
        </TableHead>
        {/* ボディ */}
        <TableBody>
          {/* rows(行が格納された配列をループ処理) */}
          {rows.map((row, index) => {
            return (
              <TableRow
                key={`index-${row.no}`}
              >
                <TableCell align="center">{row.no}</TableCell>
                <TableCell align="center">{row.name}</TableCell>
                <TableCell align="center">{row.age}</TableCell>
                <TableCell align="center">{row.note}</TableCell>
                <TableCell align="center">
                  <Button onClick={() => deleteTarget(index)}>削除</Button>
                </TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </>
  );
};

export default App;

最後に

ここまで読んでいただき、ありがとうございます。

MUIはすごく便利なライブラリでその中でもテーブルはかっこよくて、拡張性も高いです。

たくさん拡張して自分だけのテーブルを作るのも面白いと思います。

コメント

タイトルとURLをコピーしました