[React + MUI] 汎用的なレスポンシブテーブルをつくる

こんにちは、広野です。

レスポンシブデザインをご存知でしょうか。スマホの台頭により、これまで PC でないと表示できなかったような画面がモバイルデバイスでも表示できるようになりました。しかしモバイルデバイスは可搬性を考えるとどうしても画面は小さくなります。そのため、同じ WEB サイトでもユーザのデバイスによって画面デザインを変えることが求められるようになってきました。

PC 用、スマホ用で URL を変えることはしたくありません。そこで、ユーザは自分のデバイスのことを一切気にせず、アクセスさえすればアプリ側で自動的に画面デザインを変えてくれるレスポンシブデザインが生まれました。

本記事では、React アプリに MUI という画面パーツを使ってレスポンシブデザインの表(テーブル)をどのようにつくるか、解説したいと思います。

つくりたい画面

同じ WEB サイト、URL なのに、ユーザの画面サイズによって以下のようにテーブルのデザインが変わります。

テーブルに表示するデータを JSON 形式で用意してみます。

  • 表のタイトル
  • 項目名は配列
  • データは配列
{
  "title": "レスポンシブテストテーブル",
  "head": ["No.","名前","生息地","料理"],
  "data": [
    ["1","いか","海","いかめし"],
    ["2","たこ","海","たこわさ"],
    ["3","まぐろ","海","鉄火巻"],
    ["4","めだか","川","普通食べない"]
  ]
}

PC 用画面

PC で表示すると、普通の表になります。

モバイルデバイス用画面

モバイルデバイスで表示すると、PC では1行に表示されていたデータが縦に並びます。同じデータでも、PC 用は CSV のように横並びで、モバイルデバイス用は JSON のように縦並びだと言えるでしょう。

環境

本記事では、テーブルのデータ (JSON) は Amazon S3 に置いています。それを React アプリから読み込む環境になっています。

環境に関する説明は以下の記事で紹介していますので興味ある方はご覧ください。

サンプルコード

テーブル生成に関するコードのみになります。

ここでは JSON データのファイル名を変数化しています。

import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';
import { blue } from '@mui/material/colors';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';

const CreateTable = (props) => {
  //定数定義
  const filename = "xxxxx.json";
  const imgUrl = process.env.REACT_APP_IMG_URL;
  const StyledTableCell = styled(TableCell)(({ theme }) => ({
    [`&.${tableCellClasses.head}`]: {
      backgroundColor: blue[500],
      color: theme.palette.common.white
    },
    [`&.${tableCellClasses.body}`]: {
      fontSize: 14,
    }
  }));
  const StyledTableRow = styled(TableRow)(({ theme }) => ({
    '&:nth-of-type(odd)': {
      backgroundColor: theme.palette.action.hover
    }
  }));
  //state定義
  const [tableData, setTableData] = useState([]);
  //tableデータ取得関数
  const getTableData = async () => {
    const res = await axios.get(imgUrl + "/data/" + filename);
    setTableData(res.data);
  };
  //filenameが変わったとき
  useEffect(() => {
    //tableデータロード
    getTableData();
  }, [filename]);

  return (
    {(tableData) && (
      <React.Fragment>
        {/* Table Title */}
        <Typography variant="h5" component="h2" color="primary" style={{display:"flex",justifyContent:"center"}}>{tableData.title}</Typography>
        {/* Table for PC */}
        <TableContainer component={Paper} sx={{display:{xs:"none",sm:"flex"}}}>
          <Table size="small">
            <TableHead>
              <TableRow>
                {tableData.head?.map((value) => <StyledTableCell align="center">{value}</StyledTableCell>)}
              </TableRow>
            </TableHead>
            <TableBody>
              {tableData.data?.map((row) => (
                <StyledTableRow>
                  {row?.map((value) => <StyledTableCell align="center">{value}</StyledTableCell>)}
                </StyledTableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
        {/* Table for Mobile */}
        <TableContainer component={Paper} sx={{display:{xs:"flex",sm:"none"}}}>
          <Table size="small">
            <TableBody>
              {tableData.data?.map((row) => (
                <StyledTableRow>
                  <TableContainer>
                    <Table size="small" style={{tableLayout:"fixed"}}>
                      <TableBody>
                        {row?.map((value, index) => (
                          <TableRow>
                            <TableCell component="th" scope="row" style={{width:"30%"}}>{tableData.head[index]}</TableCell>
                            <TableCell>{value}</TableCell>
                          </TableRow>
                        ))}
                      </TableBody>
                    </Table>
                  </TableContainer>
                </StyledTableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
      </React.Fragment>
    )}
  );
};

export default CreateTable;

サンプルコード解説

  • ごちゃごちゃと書いていますが、テーブルのデータは Amazon S3 から GET メソッドを使って取得します。
  • テーブルのデータは、tableData というステートに格納されます。それに反応して、表が生成されます。
  • tableData には title というキーでテーブル名が格納されていますので、それをそのままテーブルのタイトルに当て込みます。

ここからがレスポンシブテーブルの作り方です。

テーブル表示位置に、PC 用テーブルのコード(コメントは Table for PC)とモバイルデバイス用テーブルのコード(コメントは Table for Mobile)を羅列しています。

何も条件がなければ両方のテーブルが縦に並んで表示されるはずですが、画面サイズによりどちらかの表示が消えます。その制御が以下の部分に入っています。

  • PC 用テーブル
<TableContainer component={Paper} sx={{display:{xs:"none",sm:"flex"}}}>
  • モバイルデバイス用テーブル
<TableContainer component={Paper} sx={{display:{xs:"flex",sm:"none"}}}>

このコードは、以下の意味を持っています。

  • PC 用テーブルは、画面サイズが “xs” だったら表示しない
  • モバイルデバイス用テーブルは、画面サイズが “sm” だったら表示しない(それ以上の大きさでも表示しない)

画面サイズは、横幅で大きく5つの分類になっています。※厳密にはピクセル単位の設定があります。

  • xs (モバイルデバイスをイメージ)
  • sm (タブレットや小さい PC モニター)
  • md (普通の PC モニター)
  • lg (大型の PC モニター)
  • xl (超デカっ)

この設定により、ユーザデバイスの画面サイズにより自動的に表示されるテーブルが選ばれます。

このテーブルには、もう1つ面白い仕掛けをしてあります。

渡すデータの行/列の数を問わず、適正な配列データさえ渡せば行/列の数を自動的に増減してくれる、汎用的なテーブルにしてあります。

PC 用テーブルは特段難しいものではないのですが、モバイルデバイス用テーブルは横並びのデータを縦並びにする工夫が必要でした。是非コードを読み解いて、秘密を解き明かして頂けたらと思います。

まとめ

いかがでしたでしょうか?

React で書かれたレスポンシブテーブルのいいサンプルをネット上で見つけられなかったため、自分でつくってみました。レスポンシブテーブルの作り方はこれだけではありません。今回はテーブル全体でまるっと表示する/しないを切り替えましたが、テーブルの列単位で表示する/しないを制御することもできます。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWSサーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリや教育コンテンツをつくっています。ReactでSPAを書き始めたら、快適すぎて他の開発言語には戻れなくなりました。AWSサーバーレスやReactの仲間を増やしたいです。
取得資格:AWS認定は7つ、ITサービスマネージャ、ITIL v3 Expert、等
2020, 2021 APN AWS Top Engineers 受賞
2022 AWS Partner Ambassador 受賞
好きなAWSサービス:AWS Amplify / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
その他技術ナレッジ
TechHarmony
タイトルとURLをコピーしました