React 初心者が Material-UI で今どきの Web フォームを作ってみた(Stepper編)

React 初心者が、Material-UI と React Hook Form v7 を活用して今どきの Web フォーム開発に挑んでみました!
2021.07.30

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

この7月からカスタマーサクセスという役割にロールチェンジしました、下田です。

つい先日、React(+ React Hook Form)と Material-UI を組み合わせて Web アプリの開発を始めました。 Web アプリ開発の初心者でも簡単に、かつ今っぽい(筆者個人の感想です)Web フォームを作ることができたので、サンプルコードと合わせて作り方を紹介してみたいと思います。イメージは、下記のとおりです。

1-5-640x264.png

作ってみた

筆者の開発環境は、下記のとおりです。

$ sw_vers 
ProductName:	macOS
ProductVersion:	11.4
BuildVersion:	20F71
$ node -v
v14.11.0
$ npm -v
6.14.8

まずは、Create React App でアプリの雛形を作成します。

※ React アプリ開発環境の構築手順については、割愛させていただきます。

$ npx create-react-app form-app
: 
$ cd form-app
$ npm start

ブラウザで、下記のページが表示されれば雛形の生成は完了です。

2-6-640x474.png

事前準備

今回の React アプリ(Web フォーム)では、Material-UI および React Hook Form v7 を利用するため、事前にライブラリをインストールしておきます。

$ npm install @material-ui/core
$ npm install react-hook-form
$ git diff -p
:
snip
:
diff --git a/package.json b/package.json
index f61e3cb..9c49442 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,13 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@material-ui/core": "^4.12.2",
     "@testing-library/jest-dom": "^5.14.1",
     "@testing-library/react": "^11.2.7",
     "@testing-library/user-event": "^12.8.3",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
+    "react-hook-form": "^7.12.1",
     "react-scripts": "4.0.3",
     "web-vitals": "^1.1.2"
   },

パッケージがインストールされ、package.json に追加されました。

ファイル構成

create-react-app コマンドにより、自動生成されたソースツリーを確認してみます。

$ tree src
src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js

0 directories, 8 files

新しく追加するソースを、どこに配置するかを検討するにあたって React 公式のドキュメントを参照すると、一般的によく用いられる2種類のファイル管理方法が紹介されていました。

今回は、「ファイルタイプ別にグループ化する」方法を用いてソースを管理するため components ディレクトリを src ディレクトリ配下に配置したいと思います。

$ mkdir src/components

ベースとなる画面構成を作成する

自動生成された App.js を、Header コンポーネントと Content コンポーネントで構成するためにテキストエディターで下記のように編集します。

App.js

import './App.css';
import { Grid } from '@material-ui/core';
import Header from './components/Header';
import Content from './components/Content';

function App() {
  return (
    <Grid container direction="column">
      <Header />
      <div style={{ padding: 30 }}>
        <Content />
      </div>
    </Grid>
  );
}
export default App;

次に、Header コンポーネントと Content コンポーネントを定義していきます。まずはファイルを用意しましょう。

$ touch src/components/Header.js
$ touch src/components/Content.js

Header.js は下記のとおり。

Header.js

import React from "react";
import { AppBar, Toolbar } from "@material-ui/core";

const Header = () => {
  return (
    <AppBar position="static" style={{ backgroundColor: "#000000" }}>
      <Toolbar>
        <img src="https://classmethod.jp/wp-content/themes/classmethod/img/common/logo_classmethod.svg" alt="クラスメソッド株式会社"></img>
      </Toolbar>
    </AppBar>
  );
};

export default Header;

Content.js は下記のとおり。

Content.js

import { Grid } from '@material-ui/core'

function Content() {
    return (
        <Grid container>
            <Grid sm={2}/>
            <Grid lg={8} sm={8} spacing={10}>
                コンテンツ
            </Grid>
        </Grid>
    )
}

export default Content

ブラウザで、確認してみます。

3-6-640x474.png

それらしくなりましたね。

画面上のコンポーネント配置としては、下記のイメージです。

4-6-640x474.png

次のステップで、Content.js に Stepper コンポーネントを配置していきます。

テキストエディターに戻って、Content.js を編集していきましょう。内容は、Material-UI 公式ドキュメントに掲載されているサンプルのソースコードをベースにしています。

Content.js

import React from 'react';
import { Grid } from '@material-ui/core'
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

function getSteps() {
    return [
        'フォーム 1',
        'フォーム 2',
        'フォーム 3'
    ];
}

function getStepContent(stepIndex) {
    switch (stepIndex) {
        case 0:
            return 'フォーム 1 のコンテンツを表示';
        case 1:
            return 'フォーム 2 のコンテンツを表示';
        case 2:
            return 'フォーム 3 のコンテンツを表示';
        default:
            return 'Unknown stepIndex';
    }
}

function Content() {
    const [activeStep, setActiveStep] = React.useState(0);
    const steps = getSteps();
    const handleNext = () => {
        setActiveStep((prevActiveStep) => prevActiveStep + 1);
    };
    const handleBack = () => {
        setActiveStep((prevActiveStep) => prevActiveStep - 1);
    };
    const handleReset = () => {
        setActiveStep(0);
    };
    return (
        <Grid container>
            <Grid sm={2}/>
            <Grid lg={8} sm={8} spacing={10}>
                <Stepper activeStep={activeStep} alternativeLabel>
                    {steps.map((label) => (
                        <Step key={label}>
                            <StepLabel>{label}</StepLabel>
                        </Step>
                    ))}
                </Stepper>
                {activeStep === steps.length ? (
                    <div>
                        <Typography >全ステップの表示を完了</Typography>
                        <Button onClick={handleReset}>リセット</Button>
                    </div>
                ) : (
                    <div>
                        <Typography >{getStepContent(activeStep)}</Typography>
                        <Button
                            disabled={activeStep === 0}
                            onClick={handleBack}
                        >
                            戻る
                        </Button>
                        <Button variant="contained" color="primary" onClick={handleNext}>
                            {activeStep === steps.length - 1 ? '送信' : '次へ'}
                        </Button>
                    </div>
                )}
            </Grid>
        </Grid>
    )
}

export default Content

再度、ブラウザに戻って画面遷移を確認してみます。

1つ目のステップ(フォーム)を表示してみます。

5-4-640x474.png

次へボタンをクリックすると、ステップ1のアイコンがチェック表示に変わりフォーム2のコンテンツが表示されます。

6-5-640x474.png

さらに、次へボタンをクリックするとステップ2のアイコンがチェック表示に変わりフォーム3のコンテンツが表示されます。

7-4-640x474.png

最後に送信ボタンをクリックするとステップ3のアイコンがチェック表示に変わり「全ステップの表示を完了」というコンテンツと「リセット」と表示されたテキストが表示されます。

8-4-640x474.png

「リセット」と表示されたテキストをクリックすると、フォーム1のコンテンツに戻ります。

Content.js を簡単に説明してみると、以下のような感じです。

  • Stepper コンポーネントの数字が表示されたステップアイコン下部に表示されるラベルは、getSteps 関数で定義される
  • React のステートとして、Stepper コンポーネントのインデックス番号(activeStep)を管理
  • ボタンクリックに応じて、setActiveStep でインデックス番号(activeStep)を増減させる
  • インデックス番号(activeStep)に応じて、getStepContent 関数で表示するコンテンツを切り替える

今回のサンプルでは、Material-UI 公式ドキュメントに掲載されているサンプルのソースコードをベースとしているため 3ステップの Web フォーム(のベースとなる画面)を作成しました。 必要に応じて Content.js に定義されている getSteps 関数および getStepContent 関数へラベルとコンテンツを追加することにより 4ステップ以上の Web フォームも気軽に作成できます。 Header.js の弊社企業ロゴを差し替えたり、試しに 4ステップ以上の構成に変更してみるなどオリジナルの Web フォーム(のベースとなる画面)を作ってみるのはいかがでしょうか。

さいごに

遅ればせながら React アプリ開発に入門してみました。React による Web アプリ開発に対する個人的な感想は、以下のとおりです。

  • 各種公式のドキュメントが和訳されており、充実している印象
  • Material-UI の完成度が高く、オシャレな部品が充実している
  • React 自体の学習コストは高いものの少ないコード量で Web アプリ開発ができそう
  • 何より手元のブラウザで動作を確認しながら、気軽に開発できて楽しい

本記事は、Stepper編ということで今どきの Web フォームを作るための準備部分を主に説明しました。React Hook Form と Material-UI を組み合わせた入力フォームの実装については以下の記事をご参照ください。

ではでは

参考情報