SPAで作るタスク管理アプリのチュートリアル - 一覧テーブル編
今回は第③弾でReactで一覧テーブル作成する部分をやっていくで
①環境構築(Docker/Laravel/React.js/Material-UI)
②React側でルーティング設定
③Reactで一覧テーブル作成
④seederで作ったDBのデータをReactに渡して一覧に表示
⑤新規登録機能
⑥編集・削除機能
map()とコンポーネント分割の話が中心やで
こんな感じで一覧表示するものを作っていくわな。

まず表示できるものをコピペしてから、
現場でよく使われる書き方として再利用しやすいコンポーネント分割や
データの受け渡しを意識した記述にリファクタリングしながら知識習得していくな。
React.jsで一覧テーブルを表示する
まず表示させてから追って解説してく感じにするな。
今あるjs/Home.jsを下記のようにそっくり入れ替えてOKやで。
import React from 'react';
import { Button, Card } from '@material-ui/core';
import { makeStyles, createStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import purple from '@material-ui/core/colors/purple';
//スタイルの定義
const useStyles = makeStyles((theme) => createStyles({
    card: {
        margin: theme.spacing(5),
        padding: theme.spacing(3),
    },
    table: {
        minWidth: 650,
      },
    tableHead: {
        backgroundColor: purple['A100'],
    },
}));
//ヘッダーのコンテンツ用の配列定義
const headerList = ['名前', 'タスク内容', '編集', '完了'];
function Home() {
    //定義したスタイルを利用するための設定
    const classes = useStyles();
    return (
        <div className="container">
            <div className="row justify-content-center">
                <div className="col-md-10">
                    <div className="card">
                        <h1>タスク管理</h1>
                        <Card className={classes.card}>
                            {/* テーブル部分の定義 */}
                            <TableContainer component={Paper}>
                                <Table className={classes.table} aria-label="simple table">
                                    {/* ヘッダー部分 */}
                                    <TableHead className={classes.tableHead}>
                                        <TableRow>
                                            {headerList.map((item, index) => (
                                                <TableCell align="center" key={index}>{item}</TableCell>
                                            ))}
                                        </TableRow>
                                    </TableHead>
                                     {/* ボディ部分 */}
                                    <TableBody>
                                        <TableRow>
                                            <TableCell align="center">モーリー</TableCell>
                                            <TableCell align="center">肩トレ</TableCell>
                                            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
                                            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
                                        </TableRow>
                                        <TableRow>
                                            <TableCell align="center">ドンキーコング</TableCell>
                                            <TableCell align="center">バナナ補給</TableCell>
                                            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
                                            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
                                        </TableRow>
                                    </TableBody>
                                </Table>
                            </TableContainer>
                        </Card>
                    </div>
                </div>
            </div>
        </div>
    );
}
export default Home;
ビルド
$ make npm-dev
これで一旦localhostにアクセスしたら表示はできるようになったと思うわ
いきなり貼っつけてなんやんねんって感じやろうから上のコードについてこれから説明行くで
makeStylesとcreateStylest
import { makeStyles, createStyles } from '@material-ui/core/styles';
@material-uiを使うためにimportしてるね。
makeStylesとcreateStylesはReactで使えるcssをいい感じに当てるために使うもんやと思っといていいと思う。
importする → useStylesを定義 → コンポーネント内でuseStyles()する → classNameに指定する
って感じで使えるで。
cardって定義したやつに注目すると下記みたいになってるで
//cardに関係ある部分だけ抜粋
import { makeStyles, createStyles } from '@material-ui/core/styles'; //import
↓
const useStyles = makeStyles((theme) => createStyles({
    card: {                             //useStylesとして定義
        margin: theme.spacing(5),
        padding: theme.spacing(3),
    },
↓
function Home() {
    const classes = useStyles(); //Homeコンポーネント内でclassesとして定義
↓
  <Card className={classes.card}>  //classNameに指定
理屈が知りたい人はMaterial-UIの公式ドキュメントをしっかり読めばいいと思うけど、
使っていくうちになれるから、このへんはなんとなくこんなふうに定義して呼び出すんやなーと思っといたら大丈夫やと思うわ。
適当に別のスタイル定義してコンポーネントに当てたりしてUI変えてみると理解深まると思うで
TableのStyleについて
これもmaterial-UIのコンポーネントを呼び出して使ってる感じ屋根
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
これも公式ドキュメントにそれぞれのコンポーネントについてわかりやすく解説してあるから、
コンポーネントごとの詳細な仕様とかはそっち見てもらったらいいと思うわ
https://material-ui.com/ja/components/tables/
----------------------------
<Table className={classes.table} aria-label="simple table">
    {/* ヘッダー部分 */}
    <TableHead className={classes.tableHead}>
        <TableRow>
            {headerList.map((item, index) => (
                <TableCell align="center" key={index}>{item}</TableCell>
            ))}
        </TableRow>
    </TableHead>
     {/* ボディ部分 */}
    <TableBody>
        <TableRow>
            <TableCell align="center">モーリー</TableCell>
            <TableCell align="center">肩トレ</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
        <TableRow>
            <TableCell align="center">ドンキーコング</TableCell>
            <TableCell align="center">バナナ補給</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
    </TableBody>
</Table>
--------------
構成を抜き出すと下記の構成になってるね。
<Table className={classes.table} aria-label="simple table">
    <TableHead>
    </TableHead>
    <TableBody>
    </TableBody>
</Table>
その他style
import Paper from '@material-ui/core/Paper';
import purple from '@material-ui/core/colors/purple';
色とかデザイン関連のものも呼び出して使える感じやね。
ヘッダーに使用しているmapメソッドについて理解する
mapはReact使ってると理解必須やし頻繁に出てくるから理解しいくで
ヘッダーに関係あるところだけ抜き出すと下記の様になるな
//ヘッダーのコンテンツ用の配列定義
const headerList = ['名前', 'タスク内容', '編集', '完了'];
↓
  {/* ヘッダー部分 */}
    <TableHead className={classes.tableHead}>
        <TableRow>
            {headerList.map((item, index) => (
                <TableCell align="center" key={index}>{item}</TableCell>
            ))}
        </TableRow>
    </TableHead>
配列で渡ってくるコンテンツをmap()で回しながら、TableRowのなかで
存在する数だけをつくっているな
mapで生成される結果については以下やな(上のコードと見比べてみてや)
    <TableRow>            
       <TableCell align="center" key="0">名前</TableCell>
       <TableCell align="center" key="1">タスク内容</TableCell>
       <TableCell align="center" key="2">編集</TableCell>
       <TableCell align="center" key="3">完了</TableCell>
    </TableRow>
map()で書くとコードがスッキリするし、数が不確定データが渡ってくることを想定すると
よく使うからmap()が分からん人はしっかり理解しとくといいで。
その上で何やけどテーブルのボディ部分についても現状TableCellを手続き的に書いていってるねんけど、ここも、Laravelからデータを受け取る想定のカタチにするためにリファクタリングしていくな。
つぎの部分からHome.jsのコードを変えてていくから差分をわかりやすくするためにコミットしておくで
$ git add .
$ git commit -m "一覧テーブルの表示"
テーブルのボディ部分をデータがバックエンドから渡ってくることを想定した定義にリファクタリング
現状のTableRowの中身は下記の通りやね
    <TableBody>
        <TableRow>
            <TableCell align="center">モーリー</TableCell>
            <TableCell align="center">肩トレ</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
        <TableRow>
            <TableCell align="center">ドンキーコング</TableCell>
            <TableCell align="center">バナナ補給</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
    </TableBody>
TableBodyの中でTableRowが渡ってくる数だけ繰り返されることになるから、rowをまとめる変数としてrowsを定義して
画像で見るとこんなイメージ
丸した部分のTableCellの中身をオブジェクトにして変数rowsの要素の一つとして扱う感じやね。
じゃあrowsを定義するわ
const headerList = ['名前', 'タスク内容', '編集', '完了'];
//headrListの下あたりにrowsを定義する
let rows = [
    {
        name: "モーリー",
        content: "肩トレ",
        editBtn: <Button color="secondary" variant="contained">編集</Button>,
        deleteBtn: <Button color="primary" variant="contained">完了</Button>,
    },{
        name: "ドンキーコング",
        content: "バナナ補給",
        editBtn: <Button color="secondary" variant="contained">編集</Button>,
        deleteBtn: <Button color="primary" variant="contained">完了</Button>,
    },
];
ボタンは固定やし渡ってくるデータじゃないから切り出す必要あるのかって思うかもしれんけど、後々テーブル部分をコンポーネントとして切り出したときに、外から渡してあげるほうが再利用性が高いから今回は切り出すことにしてるで。
作った配列をTableBodyで展開(map)していくで
旧コードの部分を下記のように変更してや
(旧コード)
-----------------------------------------------
    </TableHead>
    {/* ボディ部分 */}
    <TableBody>
        <TableRow>
            <TableCell align="center">モーリー</TableCell>
            <TableCell align="center">肩トレ</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
        <TableRow>
            <TableCell align="center">ドンキーコング</TableCell>
            <TableCell align="center">バナナ補給</TableCell>
            <TableCell align="center"><Button color="secondary" variant="contained">編集</Button></TableCell>
            <TableCell align="center"><Button color="primary" variant="contained">完了</Button></TableCell>
        </TableRow>
    </TableBody>
  </Table>
↓
(リファクタリング後コード)
-----------------------------------------------
      </TableHead>
       {/* ボディ部分 */}
       <TableBody>
          {rows.map((row, index) => (
              <TableRow key={index}> //mapで回すときはkeyが必要
                  <TableCell align="center">{row.name}</TableCell>
                  <TableCell align="center">{row.content}</TableCell>
                  <TableCell align="center">{row.editBtn}</TableCell>
                  <TableCell align="center">{row.deleteBtn}</TableCell>
              </TableRow>
          ))}
      </TableBody>
  </Table>
mapで使う形になったな。
ビルドする
$ make npm-dev
表示確認できたな。
rowの中で更にmapする
rowの中でも繰り返しが行われている箇所があるね。
それはTableRowの中で列を見たときにTableCellが繰り返されていることがわかると思う。
これはさっき定義したオブジェクトの部分なんやけど、このオブジェクトもmapしていくことにするな。
TableRowの中身を下記のように変更するで
    <TableBody>
        {rows.map((row, index) => (
            <TableRow key={index}>
                {Object.keys(row).map(function(key, i) {
                    return(
                        <TableCell align="center" key={i}>{row[key]}</TableCell>
                    );
                })}
            </TableRow>
        ))}
    </TableBody>
ちょっとわかりにくいかもしれへんけど
rowに対してmapして{name, content, editBtn, deleteBtn}をkeyとして利用している感じやね。
それでもよく分からん人は下記の記事参照すればいいと思うわ
map()使ってテーブルの中に展開する記述は現場でよく見るからここで理解しておくことを強くおすすめするね。
ビルドする
$ make npm-dev
リファクタリングしても表示できることが確認出来たらコミットしておこう
$ git add .
$ git commit -m "テーブル部分をmap利用するようにリファクタリング"
コミットまで出来たらおさらいで、リファクタリング前のコードと見比べてみると復習になると思うで。
データが2つやから恩恵がわからんかもしれんけど、バックエンドからデータが大量に渡される想定で考えたらmapで書いてるほうが扱いやすいはずやわ。
でさらに自分のエディタで変更後のテーブルブブンのソースに着目してほしいねんけど、表示コンテンツが全部変数化されてて固定値が一つもないな。
こーゆー風に表示コンテンツを外からもらう形にすることでテーブル部分だけをコンポーネント化して切り出すことができるねん。
切り出すことで、アプリケーション内の他のページで一覧表を表示したいときに再利用できて
見た目的にも統一感のあるアプリーケーションが作れるようになるで
テーブル部分をコンポーネントとして切り出す
/conponents/MainTable.jsを作成する
componentsディレクトリは現状空ディレクトリとして存在するはずやからそこに作っていくで
切り出すのはの中からで
Home.jsから切り取って以下のように設定するで
function MainTable() {
    return (
        <TableContainer component={Paper}>
            <Table className={classes.table} aria-label="simple table">
                {/* ヘッダー部分 */}
                <TableHead className={classes.tableHead}>
                    <TableRow>
                        {headerList.map((item, index) => (
                            <TableCell align="center" key={index}>{item}</TableCell>
                        ))}
                    </TableRow>
                </TableHead>
                {/* ボディ部分 */}
                <TableBody>
                    {rows.map((row, index) => (
                        <TableRow key={index}>
                            {Object.keys(row).map(function(key, i) {
                                return(
                                    <TableCell align="center" key={i}>{row[key]}</TableCell>
                                );
                            })}
                        </TableRow>
                    ))}
                </TableBody>
            </Table>
        </TableContainer>
    );
}
export default MainTable;
コンポーネントの中身が移動出来たらimport部分とスタイル部分をHome.jsから移してくるで。
HomeとMainTable両方で使ってるものはコピーしてHomeでは使わなくなったものは切り取って来る。
それぞれ↓みたいになるで
import React from 'react';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import purple from '@material-ui/core/colors/purple';
import { makeStyles, createStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => createStyles({
    card: {
        margin: theme.spacing(5),
        padding: theme.spacing(3),
    },
    table: {
        minWidth: 650,
      },
    tableHead: {
        backgroundColor: purple['A100'],
    },
}));
function MainTable(props) {
    //定義したスタイルを利用するための設定
    const classes = useStyles();
//    --------以下は既に移してきているところ
    return (
        <TableContainer component={Paper}>
Home.jsに残った部分は以下のようになるで
import React from 'react';
import { Button, Card } from '@material-ui/core';
import { makeStyles, createStyles } from '@material-ui/core/styles';
import MainTable from '../components/MainTable';
//スタイルの定義
const useStyles = makeStyles((theme) => createStyles({
    card: {
        margin: theme.spacing(5),
        padding: theme.spacing(3),
    },
}));
// ------------- 以下の部分は変更無し
//ヘッダーのコンテンツ用の配列定義
const headerList = ['名前', 'タスク内容', '編集', '完了'];
これでコンポーネントの切り出しは出来たで。
ただこれでは動かないから、Home.jsの中でMainTable.jsを使う設定をして
MainTable.jsに変数を渡してあげる必要があるで
import React from 'react';
import { Button, Card } from '@material-ui/core';
import { makeStyles, createStyles } from '@material-ui/core/styles';
import MainTable from '../components/MainTable'; // 追記する
------------------------------------
              //テーブルを切り出した部分でMainTableコンポーネントを利用し変数を渡す
                        <h1>タスク管理</h1>
                        <Card className={classes.card}>
                            {/* テーブル部分の定義 */}
                            <MainTable headerList={headerList} rows={rows} /> //追記する    
                        </Card>
                    </div>     
function MainTable(props) {
    //定義したスタイルを利用するための設定
    const classes = useStyles();
    //親コンポーネントからpropsで受け取る
    const {headerList, rows} = props;    //追記する部分
    return (
        <TableContainer component={Paper}>
Home.jsにMainTableコンポーネントを呼び出す記述を追加してを呼び出す。
その際にheaderList, rowsを渡して上げる必要がある
MainTable.js側では親コンポーネント(Home.js)で渡した変数をpropsとして受け取るで。
この辺の知識が浅い人はReactそのものの基礎知識やから、React単体のチュートリアルとかで知識補っといてな。
ビルドする
$ make npm-dev
localhostにアクセスして、表示確認できたOKやで
コミットしておこう
$ git add .
$ git commit -m "MainTableコンポーネントとして切り出す"
今回出てきたmap()やコンポーネント間のデータの受け渡しは、React案件では基礎知識になるからわからんかった人はReact単体の学習教材等で知識補ったらええと思うわ。
Material-UIの使い方もマスターしておきたいところやね。
じゃあ次はいよいよ今回の一覧表にLaravel側からデータを渡して表示する部分をやっていくで。
ほなLGTMよろしゅーやで。



