LoginSignup
1
0

More than 1 year has passed since last update.

Reactでアプリを作成しました【7】【マルチステップの入力フォーム①】

Last updated at Posted at 2021-10-03

【要件】

1. 基本情報入力画面

(1) 性別と生年月日を入力できる
(2) 『進む』ボタン

2. アンケート画面

(1) 最初に表示されている設問は1つのみ
(2) 設問に答えると次の設問が表示される
(3) 『戻る』ボタンと『進む』ボタン

3. 相談内容入力画面

(1) Textareaに自由に入力できる
(2) 『戻る』ボタンと『進む』ボタン
(3) 『進む』ボタンは動作しなくても良い

4. 確認画面

(1) 入力した内容が表示される
(2) 『戻る』ボタンと『送信』ボタン
(3) 『送信』ボタンは動作しなくても良い

【作成にあたり学習した事】

  • React Routerを使用したページ遷移
  • Reduxを使用した状態管理

1. 基本情報入力画面

create React App でアプリの雛形を作成する。

$ npx create-react-app <アプリ名>

事前準備をする。

Material-UI および React Hook Form v7 を利用するため、事前にライブラリをインストールする。
$ npm install @mui/material @emotion/react @emotion/styled
$ npm install @mui/material @mui/styled-engine-sc styled-components
$ 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"
   },

『ファイルタイプ別にグループ化する』方法を用いてソースを管理するため components ディレクトリを src ディレクトリ配下に配置する。

$ mkdir src/components

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

自動生成された App.js を、Header コンポーネントと Content コンポーネントで構成するためにテキストエディターで下記のように編集する。
App.js
import { Grid } from '@mui/material';
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
import React from "react";
import { AppBar, Toolbar } from "@mui/material";

const Header = () => {
  return (
    <AppBar position="static" style={{ backgroundColor: "primary" }}>
      <Toolbar>React課題 </Toolbar>
    </AppBar>
  );
};

export default Header;

Content.js に Stepper コンポーネントを配置する。

テキストエディターに戻って、Content.js を編集する。
『進む』『戻る』『送信』ボタン作成する

※内容は、Material-UI 公式ドキュメントに掲載のサンプルのソースコードをベースにしする。

getStepContent 関数は、React が管理している Stepper コンポーネントのインデックス番号(activeStep)に応じたコンテンツを取得する処理を記述する。そのため、各インデックス番号に応じたコンテンツ(コンポーネント)を下記のように返す。

Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";

function getSteps() {
  return ["お客様の情報を入力してください", "以下にお答えください", "ご相談ください","以下の内容をご確認ください"];
}

function getStepContent(stepIndex) {
  switch (stepIndex) {
    case 0:
      return "フォーム 1 のコンテンツを表示";
    case 1:
      return "フォーム 2 のコンテンツを表示";
    case 2:
      return "フォーム 3 のコンテンツを表示";
    case 3:
      return "フォーム 4 のコンテンツを表示";
    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.基本情報入力画面 2.アンケート画面 3. 相談内容入力画面

case 0: return 1.基本情報入力画面; case 1: return 2.アンケート画面;

case 2: return 3.相談内容入力画面; を編集する。

Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

function getSteps() {
  return ["お客様の情報を入力してください", "以下にお答えください", "ご相談ください"];
}

function getStepContent(stepIndex) {
  switch (stepIndex) {
    case 0:
      return (
        <>
          <div>
            <FormControl component="fieldset">
              <FormLabel component="legend">- 性別 -</FormLabel>
              <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
                <FormControlLabel value="male" control={<Radio />} label="男性" />
                <FormControlLabel value="female" control={<Radio />} label="女性" />
              </RadioGroup>
            </FormControl>
          </div>
          <div>
            <FormLabel component="legend">- 生年月日 -</FormLabel>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">year</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="year">
                  <option value={1}> 1990</option>
                  <option value={2}> 1991</option>
                  <option value={3}> 1992</option>
                  <option value={4}> 1993</option>
                  <option value={5}> 1994</option>
                  <option value={6}> 1995</option>
                  <option value={7}> 1996</option>
                  <option value={8}> 1997</option>
                  <option value={9}> 1998</option>
                  <option value={10}> 1999</option>
                  <option value={11}> 2000</option>
                  <option value={12}> 2001</option>
                  <option value={13}> 2002</option>
                  <option value={14}> 2003</option>
                  <option value={15}> 2004</option>
                  <option value={16}> 2005</option>
                  <option value={17}> 2006</option>
                  <option value={18}> 2007</option>
                  <option value={19}> 2008</option>
                  <option value={20}> 2009</option>
                  <option value={21}> 2010</option>
                  <option value={22}> 2011</option>
                  <option value={23}> 2012</option>
                  <option value={24}> 2013</option>
                  <option value={25}> 2014</option>
                  <option value={26}> 2015</option>
                  <option value={27}> 2016</option>
                  <option value={28}> 2017</option>
                  <option value={29}> 2018</option>
                  <option value={30}> 2019</option>
                  <option value={31}> 2020</option>
                  <option value={32}> 2021</option>
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">month</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="month">
                  <option value={1}> 1</option>
                  <option value={2}> 2</option>
                  <option value={3}> 3</option>
                  <option value={4}> 4</option>
                  <option value={5}> 5</option>
                  <option value={6}> 6</option>
                  <option value={7}> 7</option>
                  <option value={8}> 8</option>
                  <option value={9}> 9</option>
                  <option value={10}> 10</option>
                  <option value={11}> 11</option>
                  <option value={12}> 12</option>
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">day</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="day">
                  <option value={1}> 1</option>
                  <option value={2}> 2</option>
                  <option value={3}> 3</option>
                  <option value={4}> 4</option>
                  <option value={5}> 2</option>
                  <option value={6}> 6</option>
                  <option value={7}> 7</option>
                  <option value={8}> 8</option>
                  <option value={9}> 9</option>
                  <option value={10}> 10</option>
                  <option value={11}> 11</option>
                  <option value={12}> 12</option>
                  <option value={13}> 13</option>
                  <option value={14}> 14</option>
                  <option value={15}> 15</option>
                  <option value={16}> 16</option>
                  <option value={17}> 17</option>
                  <option value={18}> 18</option>
                  <option value={19}> 19</option>
                  <option value={20}> 20</option>
                  <option value={21}> 21</option>
                  <option value={22}> 22</option>
                  <option value={23}> 23</option>
                  <option value={24}> 24</option>
                  <option value={25}> 25</option>
                  <option value={26}> 26</option>
                  <option value={27}> 27</option>
                  <option value={28}> 28</option>
                  <option value={29}> 29</option>
                  <option value={30}> 30</option>
                  <option value={31}> 31</option>
                </optgroup>
              </Select>
            </FormControl>
          </div>
        </>
      );
    case 1:
      return (
        <div>
          <FormControl component="fieldset">
            <FormLabel component="legend">現在生命保険に加入されていますか</FormLabel>
            <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
              <FormControlLabel value="yes" control={<Radio />} label="はい" />
              <FormControlLabel value="no" control={<Radio />} label="いいえ" />
            </RadioGroup>

            <FormLabel component="legend">
              現在入院中ですかまた3ヶ月以内に医師の診察検査の結果入院手術をすすめられたことがありますか
            </FormLabel>
            <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
              <FormControlLabel value="yes" control={<Radio />} label="はい" />
              <FormControlLabel value="no" control={<Radio />} label="いいえ" />
            </RadioGroup>

            <FormLabel component="legend">
              過去5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか
            </FormLabel>
            <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
              <FormControlLabel value="yes" control={<Radio />} label="はい" />
              <FormControlLabel value="no" control={<Radio />} label="いいえ" />
            </RadioGroup>
          </FormControl>
        </div>
      );
    case 2:
      return (
        <Grid container>
          <Grid sm={2} />
          <Grid lg={8} sm={8} spacing={10}>
            <Tooltip title="ご相談内容を記入することができます" placement="top-start" arrow>
              <TextField
                label="ご相談内容"
                fullWidth
                margin="normal"
                rows={4}
                multiline
                variant="outlined"
                placeholder="その他ご要望等あれば、ご記入ください"
              />
            </Tooltip>
          </Grid>
        </Grid>
      );
    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;  

2. アンケート画面を編集

(1) 最初に表示されている設問は1つのみ (2) 設問に答えると次の設問が表示される

下記の3点を修正する。
1.switch (stepIndex) の case 1:でreturnしていたコンポーネント全体を、個別のコンポーネント Questionnaire として切り出す。
2.Content のstate として、アンケートの各設問の回答を配列answersを持たせるようにして、この配列と配列を更新する関数 setAnswersを props 経由で、Questionnaireコンポーネントまで渡すようにする。
3. 関数であったgetStepContentを、StepContentコンポーネントにする。

Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

const QUESTIONS = [
  "現在、生命保険に加入されていますか?",
  "現在、入院中ですか。また、3ヶ月以内に医師の診察・検査の結果、入院・手術をすすめられたことがありますか?",
  "過去、5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか?",
];

const Questionnaire = ({ answers, setAnswers }) => {
  const handleAnswer = (answeredIndex, answer) => {
    setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)));
  };
  return (
    <div>
      <FormControl component="fieldset">
        {answers
          .filter((_, i) => i === 0 || answers[i - 1])
          .map((answer, i) => (
            <React.Fragment key={i}>
              <FormLabel component="legend">{QUESTIONS[i]}</FormLabel>
              {answer ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel value="yes" control={<Radio />} label="はい" />
                  <FormControlLabel value="no" control={<Radio />} label="いいえ" />
                </RadioGroup>
              )}
            </React.Fragment>
          ))}
      </FormControl>
    </div>
  );
};
function getSteps() {
  return ["お客様の情報を入力してください", "以下にお答えください", "ご相談ください"];
}

const StepContent = ({ stepIndex, questionnaireProps }) => {
  switch (stepIndex) {
    case 0:
      return (
        <>
          <div>
            <FormControl component="fieldset">
              <FormLabel component="legend">- 性別 -</FormLabel>
              <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
                <FormControlLabel value="male" control={<Radio />} label="男性" />
                <FormControlLabel value="female" control={<Radio />} label="女性" />
              </RadioGroup>
            </FormControl>
          </div>
          <div>
            <FormLabel component="legend">- 生年月日 -</FormLabel>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">year</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="year">
                  <option value={1}> 1990</option>
                  <option value={2}> 1991</option>
                  <option value={3}> 1992</option>
                  <option value={4}> 1993</option>
                  <option value={5}> 1994</option>
                  <option value={6}> 1995</option>
                  <option value={7}> 1996</option>
                  <option value={8}> 1997</option>
                  <option value={9}> 1998</option>
                  <option value={10}> 1999</option>
                  <option value={11}> 2000</option>
                  <option value={12}> 2001</option>
                  <option value={13}> 2002</option>
                  <option value={14}> 2003</option>
                  <option value={15}> 2004</option>
                  <option value={16}> 2005</option>
                  <option value={17}> 2006</option>
                  <option value={18}> 2007</option>
                  <option value={19}> 2008</option>
                  <option value={20}> 2009</option>
                  <option value={21}> 2010</option>
                  <option value={22}> 2011</option>
                  <option value={23}> 2012</option>
                  <option value={24}> 2013</option>
                  <option value={25}> 2014</option>
                  <option value={26}> 2015</option>
                  <option value={27}> 2016</option>
                  <option value={28}> 2017</option>
                  <option value={29}> 2018</option>
                  <option value={30}> 2019</option>
                  <option value={31}> 2020</option>
                  <option value={32}> 2021</option>
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">month</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="month">
                  <option value={1}> 1</option>
                  <option value={2}> 2</option>
                  <option value={3}> 3</option>
                  <option value={4}> 4</option>
                  <option value={5}> 5</option>
                  <option value={6}> 6</option>
                  <option value={7}> 7</option>
                  <option value={8}> 8</option>
                  <option value={9}> 9</option>
                  <option value={10}> 10</option>
                  <option value={11}> 11</option>
                  <option value={12}> 12</option>
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">day</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="day">
                  <option value={1}> 1</option>
                  <option value={2}> 2</option>
                  <option value={3}> 3</option>
                  <option value={4}> 4</option>
                  <option value={5}> 2</option>
                  <option value={6}> 6</option>
                  <option value={7}> 7</option>
                  <option value={8}> 8</option>
                  <option value={9}> 9</option>
                  <option value={10}> 10</option>
                  <option value={11}> 11</option>
                  <option value={12}> 12</option>
                  <option value={13}> 13</option>
                  <option value={14}> 14</option>
                  <option value={15}> 15</option>
                  <option value={16}> 16</option>
                  <option value={17}> 17</option>
                  <option value={18}> 18</option>
                  <option value={19}> 19</option>
                  <option value={20}> 20</option>
                  <option value={21}> 21</option>
                  <option value={22}> 22</option>
                  <option value={23}> 23</option>
                  <option value={24}> 24</option>
                  <option value={25}> 25</option>
                  <option value={26}> 26</option>
                  <option value={27}> 27</option>
                  <option value={28}> 28</option>
                  <option value={29}> 29</option>
                  <option value={30}> 30</option>
                  <option value={31}> 31</option>
                </optgroup>
              </Select>
            </FormControl>
          </div>
        </>
      );
    case 1:
      return <Questionnaire {...questionnaireProps} />;
    case 2:
      return (
        <Grid container>
          <Grid sm={2} />
          <Grid lg={8} sm={8} spacing={10}>
            <Tooltip title="ご相談内容を記入することができます" placement="top-start" arrow>
              <TextField
                label="ご相談内容"
                fullWidth
                margin="normal"
                rows={4}
                multiline
                variant="outlined"
                placeholder="その他ご要望等あれば、ご記入ください"
              />
            </Tooltip>
          </Grid>
        </Grid>
      );
    default:
      return "Unknown stepIndex";
  }
};
function Content() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));
  const steps = getSteps();
  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };
  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };
  const handleReset = () => {
    setActiveStep(0);
  };
  const buttonDisabled = activeStep === 1 && answers.some((a) => !a);
  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>
              <StepContent stepIndex={activeStep} questionnaireProps={{ answers, setAnswers }} />
            </Typography>
            <Button disabled={activeStep === 0} onClick={handleBack}>
              戻る
            </Button>
            <Button variant="contained" color="primary" onClick={handleNext} disabled={buttonDisabled}>
              {activeStep === steps.length - 1 ? "送信" : "次へ"}
            </Button>
          </div>
        )}
      </Grid>
    </Grid>
  );
}
export default Content;

1.基本情報入力画面 

components / Content.js の case 0: 以下内の生年月日を入力する現在のソースコードを該当する数字を全て記載するのではなく動的なコードに変更する。

Array.fromを使って、その場で配列を作成する。

sample.js
{
  Array.from(
    Array(12), 
    (_, num) => (<option key={num} value={num + 1}>{num + 1}</option>)
  )
}
Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

const QUESTIONS = [
  "現在、生命保険に加入されていますか?",
  "現在、入院中ですか。また、3ヶ月以内に医師の診察・検査の結果、入院・手術をすすめられたことがありますか?",
  "過去、5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか?",
];

const Questionnaire = ({ answers, setAnswers }) => {
  const handleAnswer = (answeredIndex, answer) => {
    setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)));
  };
  return (
    <div>
      <FormControl component="fieldset">
        {answers
          .filter((_, i) => i === 0 || answers[i - 1])
          .map((answer, i) => (
            <React.Fragment key={i}>
              <FormLabel component="legend">{QUESTIONS[i]}</FormLabel>
              {answer ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel value="yes" control={<Radio />} label="はい" />
                  <FormControlLabel value="no" control={<Radio />} label="いいえ" />
                </RadioGroup>
              )}
            </React.Fragment>
          ))}
      </FormControl>
    </div>
  );
};
function getSteps() {
  return ["お客様の情報を入力してください", "以下にお答えください", "ご相談ください"];
}

const StepContent = ({ stepIndex, questionnaireProps }) => {
  switch (stepIndex) {
    case 0:
      return (
        <>
          <div>
            <FormControl component="fieldset">
              <FormLabel component="legend">- 性別 -</FormLabel>
              <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
                <FormControlLabel value="male" control={<Radio />} label="男性" />
                <FormControlLabel value="female" control={<Radio />} label="女性" />
              </RadioGroup>
            </FormControl>
          </div>
          <div>
            <FormLabel component="legend">- 生年月日 -</FormLabel>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">year</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="year">
                  {Array.from(Array(2020), (_, num) => (
                    <option key={num} value={num + 1}>
                      {num + 1990}
                    </option>
                  ))}
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">month</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="month">
                  {Array.from(Array(12), (_, num) => (
                    <option key={num} value={num + 1}>
                      {num + 1}
                    </option>
                  ))}
                </optgroup>
              </Select>
            </FormControl>
            <FormControl sx={{ m: 1, minWidth: 120 }}>
              <InputLabel htmlFor="grouped-native-select">day</InputLabel>
              <Select native defaultValue="" id="grouped-native-select" label="Grouping">
                <option aria-label="None" value="" />
                <optgroup label="day">
                  {Array.from(Array(12), (_, num) => (
                    <option key={num} value={num + 1}>
                      {num + 1}
                    </option>
                  ))}
                </optgroup>
              </Select>
            </FormControl>
          </div>
        </>
      );
    case 1:
      return <Questionnaire {...questionnaireProps} />;
    case 2:
      return (
        <Grid container>
          <Grid sm={2} />
          <Grid lg={8} sm={8} spacing={10}>
            <Tooltip title="ご相談内容を記入することができます" placement="top-start" arrow>
              <TextField
                label="ご相談内容"
                fullWidth
                margin="normal"
                rows={4}
                multiline
                variant="outlined"
                placeholder="その他ご要望等あれば、ご記入ください"
              />
            </Tooltip>
          </Grid>
        </Grid>
      );
    default:
      return "Unknown stepIndex";
  }
};
function Content() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));
  const steps = getSteps();
  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };
  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };
  const handleReset = () => {
    setActiveStep(0);
  };
  const buttonDisabled = activeStep === 1 && answers.some((a) => !a);
  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>
              <StepContent stepIndex={activeStep} questionnaireProps={{ answers, setAnswers }} />
            </Typography>
            <Button disabled={activeStep === 0} onClick={handleBack}>
              戻る
            </Button>
            <Button variant="contained" color="primary" onClick={handleNext} disabled={buttonDisabled}>
              {activeStep === steps.length - 1 ? "送信" : "次へ"}
            </Button>
          </div>
        )}
      </Grid>
    </Grid>
  );
}
export default Content;

各コンテンツのコンポーネントに応じたソースファイルを用意

$ touch src/components/Basic.js
$ touch src/components/Questionnaire.js
$ touch src/components/Optional.js

各コンテンツのコンポーネントに応じたソースファイルを編集する

Content.js

Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Basic from "./Basic";
import Questionnaire, { QUESTIONS } from "./Questionnaire";
import Optional from "./Optional";

function getSteps() {
  return ["お客様の情報を入力して下さい", "以下にお答え下さい", "ご相談下さい", "以下の内容をご確認下さい"];
}

const StepContent = ({ stepIndex, questionnaireProps }) => {
  switch (stepIndex) {
    case 0:
      return <Basic />;
case 1:
      return <Questionnaire {...questionnaireProps} />;
    case 2:
      return <Optional />;
case 3:
      return <Basic />;
             <Questionnaire {...questionnaireProps} />;
             <Optional />;
    default:
      return "Unknown stepIndex";
  }
};
function Content() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));
  const steps = getSteps();
  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };
  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };
  const handleReset = () => {
    setActiveStep(0);
  };
  const buttonDisabled = activeStep === 1 && answers.some((a) => !a);
  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 style={{ textAlign: "center" }}>
            <Typography>全ステップの表示を完了</Typography>
            <Button onClick={handleReset}>リセット</Button>
          </div>
        ) : (
          <div>
            <Typography>
              <StepContent stepIndex={activeStep} questionnaireProps={{ answers, setAnswers }} />
            </Typography>
            <Button disabled={activeStep === 0} onClick={handleBack}>
              戻る
            </Button>
            <Button variant="contained" color="primary" onClick={handleNext} disabled={buttonDisabled}>
              {activeStep === steps.length - 1 ? "送信" : "次へ"}
            </Button>
          </div>
        )}
      </Grid>
    </Grid>
  );
}
export default Content;
Basic.js
Basic.js
import React from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

const Basic = () => {
  return (
    <>
      <div style={{ textAlign: "center" }}>
        <FormControl component="fieldset">
          <FormLabel component="legend">- 性別 -</FormLabel>
          <RadioGroup row aria-label="gender" name="row-radio-buttons-group">
            <FormControlLabel value="male" control={<Radio />} label="男性" />
            <FormControlLabel value="female" control={<Radio />} label="女性" />
          </RadioGroup>
        </FormControl>
      </div>
      <div style={{ textAlign: "center" }}>
        <FormLabel component="legend">- 生年月日 -</FormLabel>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">year</InputLabel>
          <Select native defaultValue="" id="grouped-native-select" label="Grouping">
            <option aria-label="None" value="" />
            <optgroup label="year">
              {Array.from(Array(2020), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1990}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">month</InputLabel>
          <Select native defaultValue="" id="grouped-native-select" label="Grouping">
            <option aria-label="None" value="" />
            <optgroup label="month">
              {Array.from(Array(12), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">day</InputLabel>
          <Select native defaultValue="" id="grouped-native-select" label="Grouping">
            <option aria-label="None" value="" />
            <optgroup label="day">
              {Array.from(Array(12), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
      </div>
    </>
  );
};

export default Basic;

Questionnaire.js

複数のコンポーネントで一つのstateを共有したい場合 ➡︎ 親のコンポーネントに置く
親のコンポーネントでstateを宣言してpropsとして渡す。

sample.js
const [answers, setAnswers] = React.useState([])

<Questionnaire answers={answers} />

どうやって子のコンポーネントで変数を書き換えるか ➡︎ ① stateを書き換える  ②関数をラップした関数を渡す
この方がstateの宣言とsetする関数が一つのコンポーネントに留まっているので可読性があがる。

sample.js
const [answers, setAnswers] = React.useState([])

const handleAnswer = (answeredIndex, answer) => {
  setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)))
}

<Questionnaire answers={answers} handleAnswer={handleAnswer} />
const Questionnaire = ({ answers, handleAnswer }) => {
    return (
        <button onClick={() => handleAnswer(answeredIndex, answer)}></button>
    )
}

Questionnaire.js
import React from "react";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";

export const QUESTIONS = [
  "現在、生命保険に加入されていますか?",
  "現在、入院中ですか。また、3ヶ月以内に医師の診察・検査の結果、入院・手術をすすめられたことがありますか?",
  "過去、5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか?",
];

const Questionnaire = ({ answers, setAnswers }) => {
  const handleAnswer = (answeredIndex, answer) => {
    setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)));
  };
  return (
    <div>
      <FormControl component="fieldset">
        {answers
          .filter((_, i) => i === 0 || answers[i - 1])
          .map((answer, i) => (
            <React.Fragment key={i}>
              <FormLabel component="legend">{QUESTIONS[i]}</FormLabel>
              {answer ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel value="yes" control={<Radio />} label="はい" />
                  <FormControlLabel value="no" control={<Radio />} label="いいえ" />
                </RadioGroup>
              )}
            </React.Fragment>
          ))}
      </FormControl>
    </div>
  );
};

export default Questionnaire;
Optional.js
Optional.js
import React from "react";
import { Grid } from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";

const Optional = () => {
  return (
    <div>
      <Grid container>
        <Grid sm={2} />
        <Grid lg={8} sm={8} spacing={10}>
          <Tooltip title="ご相談内容を記入することができます" placement="top-start" arrow>
            <TextField
              label="ご相談内容"
              fullWidth
              margin="normal"
              rows={4}
              multiline
              variant="outlined"
              placeholder="その他ご要望等あれば、ご記入ください"
            />
          </Tooltip>
        </Grid>
      </Grid>
    </div>
  );
};
export default Optional;

4. 確認画面を編集

(1) 入力した内容が表示される
(2) 『戻る』ボタンと『送信』ボタン
(3) 『送信』ボタンは動作しなくても良い

answers には質問の数だけ null が入った配列を初期値として渡す必要がある。
同様のコードはすでに contents.js の Questionnaire で書かれているので、contents.js  Basic.js Optipnal.js を編集する。

content.js
const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));

<Questionnaire answers={answers} setAnswers={setAnswers} /> 

    case 1:
      return <Questionnaire {...questionnaireProps} />;

ユーザーが React コンポーネントに与えたデータを保存するには、 state (状態)として取り扱う必要がある。
1つの state を管理するには、 contents.js の中(上記の`sample.js)ですでに一度行なっているように、

content.js
const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));

上記のように React.useState() を使って state 変数とセッター関数の組を作成する。
セッター関数でデータをセットすると次にコンポーネントが描画されるときに値が state 変数に反映される。

content.js
<StepContent stepIndex={activeStep} questionnaireProps={{ answers, setAnswers }} />

    case 1:
      return <Questionnaire {...questionnaireProps} />;

Questionnaire コンポーネントに state 渡しているコードを参考に、Basic.js Optipnal.js を編集する。

content.js
const [basicProfile, setBasicProfile] = React.useState({ gender: null, year: null, month: null, day: null ));

を Content コンポーネントの中に書いて、下記のように渡す。

content.js
<StepContent  BasicProps={{ basicProfile, setBasicProfile }} />

Basic コンポーネント側では、下記のようにすれば、受け取れる。

Basic.js
const Basic = ({ basicProfile, setBasicProfile }) => {  }

<RadioGroup  value={basicProfile.gender} onChange={(evt) =>
setBasicProfile((state) => {
return { ...state, gender: evt.target.value }
    })
}>
Basic.js
import React from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

const Basic = ({ basicProfile, setBasicProfile }) => {
  return (
    <>
      <div style={{ textAlign: "center" }}>
        <FormControl component="fieldset">
          <FormLabel component="gender">- 性別 -</FormLabel>
          <RadioGroup
            row
            aria-label="gender"
            name="row-radio-buttons-group"
            value={basicProfile.gender}
            onChange={(evt) =>
              setBasicProfile((state) => {
                return { ...state, gender: evt.target.value };
              })
            }
          >
            <FormControlLabel value="male" control={<Radio />} label="男性" />
            <FormControlLabel value="female" control={<Radio />} label="女性" />
          </RadioGroup>
        </FormControl>
      </div>
      <div style={{ textAlign: "center" }}>
        <FormLabel component="legend">- 生年月日 -</FormLabel>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">year</InputLabel>
          <Select
            native
            defaultValue=""
            id="grouped-native-select"
            label="Grouping"
            value={basicProfile.year}
            onChange={(evt) =>
              setBasicProfile((state) => {
                return { ...state, year: evt.target.value };
              })
            }
          >
            <option aria-label="None" value="" />
            <optgroup label="year">
              {Array.from(Array(2020), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1990}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">month</InputLabel>
          <Select
            native
            defaultValue=""
            id="grouped-native-select"
            label="Grouping"
            value={basicProfile.month}
            onChange={(evt) =>
              setBasicProfile((state) => {
                return { ...state, month: evt.target.value };
              })
            }
          >
            <option aria-label="None" value="" />
            <optgroup label="month">
              {Array.from(Array(12), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">day</InputLabel>
          <Select
            native
            defaultValue=""
            id="grouped-native-select"
            label="Grouping"
            value={basicProfile.day}
            onChange={(evt) =>
              setBasicProfile((state) => {
                return { ...state, day: evt.target.value };
              })
            }
          >
            <option aria-label="None" value="" />
            <optgroup label="day">
              {Array.from(Array(12), (_, num) => (
                <option key={num} value={num + 1}>
                  {num + 1}
                </option>
              ))}
            </optgroup>
          </Select>
        </FormControl>
      </div>
    </>
  );
};

export default Basic;
Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Basic from "./Basic";
import Questionnaire, { QUESTIONS } from "./Questionnaire";
import Optional from "./Optional";

function getSteps() {
  return ["お客様の情報を入力して下さい", "以下にお答え下さい", "ご相談下さい", "以下の内容をご確認下さい"];
}

const StepContent = ({ stepIndex, basicProps, questionnaireProps, optionalProps }) => {
  switch (stepIndex) {
    case 0:
      return <Basic {...basicProps} />;
    case 1:
      return <Questionnaire {...questionnaireProps} />;
    case 2:
      return <Optional {...optionalProps} />;
    case 3:
      return (
        <div style={{ textAlign: "center" }}>
          <Basic {...basicProps} />
          <Questionnaire {...questionnaireProps} />
          <Optional {...optionalProps} />
        </div>
      );
    default:
      return "Unknown stepIndex";
  }
};
function Content() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [basicProfile, setBasicProfile] = React.useState({ gender: null, year: null, month: null, day: null });
  const [answers, setAnswers] = React.useState(Array(QUESTIONS.length).fill(null));
  const [optionalRequest, setOptionalRequest] = React.useState({ request: null });
  const steps = getSteps();
  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };
  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };
  const handleReset = () => {
    setActiveStep(0);
  };
  const buttonDisabled = activeStep === 1 && answers.some((a) => !a);
  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 style={{ textAlign: "center" }}>
            <Typography>全ステップの表示を完了</Typography>
            <Button onClick={handleReset}>リセット</Button>
          </div>
        ) : (
          <div>
            <Typography>
              <StepContent
                stepIndex={activeStep}
                questionnaireProps={{ answers, setAnswers }}
                basicProps={{ basicProfile, setBasicProfile }}
                optionalProps={{ optionalRequest, setOptionalRequest }}
              />
            </Typography>
            <Button disabled={activeStep === 0} onClick={handleBack}>
              戻る
            </Button>
            <Button variant="contained" color="primary" onClick={handleNext} disabled={buttonDisabled}>
              {activeStep === steps.length - 1 ? "送信" : "次へ"}
            </Button>
          </div>
        )}
      </Grid>
    </Grid>
  );
}
export default Content;
Optional.js
import React from "react";
import { Grid } from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";

const Optional = ({ optionalRequest, setOptionalRequest }) => {
  return (
    <div>
      <Grid container>
        <Grid sm={2} />
        <Grid lg={8} sm={8} spacing={10}>
          <Tooltip
            title="ご相談内容を記入することができます"
            placement="top-start"
            arrow
            value={optionalRequest.consultation}
            onChange={(evt) =>
              setOptionalRequest((state) => {
                return { ...state, consultation: evt.target.value };
              })
            }
          >
            <TextField
              label="ご相談内容"
              fullWidth
              margin="normal"
              rows={4}
              multiline
              variant="outlined"
              placeholder="その他ご要望等あれば、ご記入ください"
            />
          </Tooltip>
        </Grid>
      </Grid>
    </div>
  );
};
export default Optional;
Questionnaire.js
import React from "react";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";

export const QUESTIONS = [
  "現在、生命保険に加入されていますか?",
  "現在、入院中ですか。また、3ヶ月以内に医師の診察・検査の結果、入院・手術をすすめられたことがありますか?",
  "過去、5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか?",
];

const Questionnaire = ({ answers, setAnswers }) => {
  const handleAnswer = (answeredIndex, answer) => {
    setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)));
  };
  return (
    <div>
      <FormControl component="fieldset">
        {answers
          .filter((_, i) => i === 0 || answers[i - 1])
          .map((answer, i) => (
            <React.Fragment key={i}>
              <FormLabel component="legend">{QUESTIONS[i]}</FormLabel>
              {answer ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel value="yes" control={<Radio />} label="はい" />
                  <FormControlLabel value="no" control={<Radio />} label="いいえ" />
                </RadioGroup>
              )}
            </React.Fragment>
          ))}
      </FormControl>
    </div>
  );
};

export default Questionnaire;

確認画面で、性別・年月日・コメントが編集できるのを改善する。

(選択されたものを表示させるだけにしたい)

Basic.js のPropsを増やす。

Basic.js
const Basic = ({ basicProfile, setBasicProfile, isConfirm }) => {

ここで追加した isConfirm がモードに該当します。
あとは性別のラジオを出していた箇所をの下記で isConfirm の値によってラジオか単なるテキストで表示するかを切り分ける。

Basic.js
{isConfirm ? (
            <span>{basicProfile?.gender === "male" ? "男性" : "女性"}</span>
          ) : (
            <RadioGroup
              row
              aria-label="gender"
              name="row-radio-buttons-group"
              value={basicProfile.gender}
              onChange={(evt) =>
                setBasicProfile((state) => {
                  return { ...state, gender: evt.target.value };
                })
              }
            >
              <FormControlLabel value="male" control={<Radio />} label="男性" />
              <FormControlLabel
                value="female"
                control={<Radio />}
                label="女性"
              />
            </RadioGroup>
          )}

その上で Content.js の case 3 で Basic を呼び出している箇所に
下記のようにで isConfirm フラグを立てる。

Content.js
<Basic isConfirm {...basicProps} />

年月日は下記のように書く。

Basic.js
<InputLabel htmlFor="grouped-native-select">year</InputLabel>
          {isConfirm ? (
            <span>{basicProfile.year}</span>
          ) : (

    //省略

        )}

質問の回答の画面で、質問に1度答えると回答を変更できないので、変更できるように実装する

Questionaire.js
{answer ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel
                    value="yes"
                    control={<Radio />}
                    label="はい"
                  />
                  <FormControlLabel
                    value="no"
                    control={<Radio />}
                    label="いいえ"
                  />
                </RadioGroup>
              )}

上記の anwser? の false 時の分岐のみを下記のように記載すれば実装可能

Questionaire.js

const Questionnaire = ({ answers, setAnswers, isConfirm }) => {

//省略

{isConfirm ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel
                    value="yes"
                    control={<Radio />}
                    label="はい"
                  />
                  <FormControlLabel
                    value="no"
                    control={<Radio />}
                    label="いいえ"
                  />
                </RadioGroup>
              )}

Content.js
<Questionnaire isConfirm {...questionnaireProps} />

完成

Basic.js

import React from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";

const Basic = ({ basicProfile, setBasicProfile, isConfirm }) => {
  return (
    <>
      <div style={{ textAlign: "center" }}>
        <FormControl component="fieldset">
          <FormLabel component="gender">- 性別 -</FormLabel>
          {isConfirm ? (
            <span>{basicProfile?.gender === "male" ? "男性" : "女性"}</span>
          ) : (
            <RadioGroup
              row
              aria-label="gender"
              name="row-radio-buttons-group"
              value={basicProfile.gender}
              onChange={(evt) =>
                setBasicProfile((state) => {
                  return { ...state, gender: evt.target.value };
                })
              }
            >
              <FormControlLabel value="male" control={<Radio />} label="男性" />
              <FormControlLabel
                value="female"
                control={<Radio />}
                label="女性"
              />
            </RadioGroup>
          )}
        </FormControl>
      </div>
      <div style={{ textAlign: "center" }}>
        <FormLabel component="legend">- 生年月日 -</FormLabel>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">year</InputLabel>
          {isConfirm ? (
            <span>{basicProfile.year}</span>
          ) : (
            <Select
              native
              defaultValue=""
              id="grouped-native-select"
              label="Grouping"
              value={basicProfile.year}
              onChange={(evt) =>
                setBasicProfile((state) => {
                  return { ...state, year: evt.target.value };
                })
              }
            >
              <option aria-label="None" value="" />
              <optgroup label="year">
                {Array.from(Array(2020), (_, num) => (
                  <option key={num} value={num + 1990}>
                    {num + 1990}
                  </option>
                ))}
              </optgroup>
            </Select>
          )}
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">month</InputLabel>
          {isConfirm ? (
            <span>{basicProfile.month}</span>
          ) : (
            <Select
              native
              defaultValue=""
              id="grouped-native-select"
              label="Grouping"
              value={basicProfile.month}
              onChange={(evt) =>
                setBasicProfile((state) => {
                  return { ...state, month: evt.target.value };
                })
              }
            >
              <option aria-label="None" value="" />
              <optgroup label="month">
                {Array.from(Array(12), (_, num) => (
                  <option key={num} value={num + 1}>
                    {num + 1}
                  </option>
                ))}
              </optgroup>
            </Select>
          )}
        </FormControl>
        <FormControl sx={{ m: 1, minWidth: 120 }}>
          <InputLabel htmlFor="grouped-native-select">day</InputLabel>
          {isConfirm ? (
            <span>{basicProfile.day}</span>
          ) : (
            <Select
              native
              defaultValue=""
              id="grouped-native-select"
              label="Grouping"
              value={basicProfile.day}
              onChange={(evt) =>
                setBasicProfile((state) => {
                  return { ...state, day: evt.target.value };
                })
              }
            >
              <option aria-label="None" value="" />
              <optgroup label="day">
                {Array.from(Array(31), (_, num) => (
                  <option key={num} value={num + 1}>
                    {num + 1}
                  </option>
                ))}
              </optgroup>
            </Select>
          )}
        </FormControl>
      </div>
    </>
  );
};

export default Basic;
Content.js
import React from "react";
import { Grid } from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Basic from "./Basic";
import Questionnaire, { QUESTIONS } from "./Questionnaire";
import Optional from "./Optional";

function getSteps() {
  return [
    "お客様の情報を入力して下さい",
    "以下にお答え下さい",
    "ご相談下さい",
    "以下の内容をご確認下さい",
  ];
}

const StepContent = ({
  stepIndex,
  basicProps,
  questionnaireProps,
  optionalProps,
}) => {
  switch (stepIndex) {
    case 0:
      return <Basic {...basicProps} />;
    case 1:
      return <Questionnaire {...questionnaireProps} />;
    case 2:
      return <Optional {...optionalProps} />;
    case 3:
      return (
        <div style={{ textAlign: "center" }}>
          <Basic isConfirm {...basicProps} />
          <Questionnaire isConfirm {...questionnaireProps} />
          <Optional isConfirm {...optionalProps} />
        </div>
      );
    default:
      return "Unknown stepIndex";
  }
};
function Content() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [basicProfile, setBasicProfile] = React.useState({
    gender: null,
    year: null,
    month: null,
    day: null,
  });
  const [answers, setAnswers] = React.useState(
    Array(QUESTIONS.length).fill(null)
  );
  const [optionalRequest, setOptionalRequest] = React.useState({
    request: null,
  });
  const steps = getSteps();
  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };
  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };
  const handleReset = () => {
    setActiveStep(0);
  };
  const buttonDisabled = activeStep === 1 && answers.some((a) => !a);
  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 style={{ textAlign: "center" }}>
            <Typography>全ステップの表示を完了</Typography>
            <Button onClick={handleReset}>リセット</Button>
          </div>
        ) : (
          <div>
            <Typography>
              <StepContent
                stepIndex={activeStep}
                questionnaireProps={{ answers, setAnswers }}
                basicProps={{ basicProfile, setBasicProfile }}
                optionalProps={{ optionalRequest, setOptionalRequest }}
              />
            </Typography>
            <Button disabled={activeStep === 0} onClick={handleBack}>
              戻る
            </Button>
            <Button
              variant="contained"
              color="primary"
              onClick={handleNext}
              disabled={buttonDisabled}
            >
              {activeStep === steps.length - 1 ? "送信" : "次へ"}
            </Button>
          </div>
        )}
      </Grid>
    </Grid>
  );
}
export default Content;
Optional.js

import React from "react";
import { Grid } from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import TextField from "@mui/material/TextField";

const Optional = ({ optionalRequest, setOptionalRequest, isConfirm }) => {
  return (
    <div>
      <Grid container>
        <Grid sm={2} />
        <Grid lg={8} sm={8} spacing={10}>
          {isConfirm ? (
            <span>{optionalRequest?.consultation}</span>
          ) : (
            <Tooltip
              title="ご相談内容を記入することができます"
              placement="top-start"
              arrow
              value={optionalRequest.consultation}
              onChange={(evt) =>
                setOptionalRequest((state) => {
                  return { ...state, consultation: evt.target.value };
                })
              }
            >
              <TextField
                label="ご相談内容"
                fullWidth
                margin="normal"
                rows={4}
                multiline
                variant="outlined"
                placeholder="その他ご要望等あれば、ご記入ください"
              />
            </Tooltip>
          )}
        </Grid>
      </Grid>
    </div>
  );
};
export default Optional;
Questionnaire.js
import React from "react";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";

export const QUESTIONS = [
  "現在、生命保険に加入されていますか?",
  "現在、入院中ですか。また、3ヶ月以内に医師の診察・検査の結果、入院・手術をすすめられたことがありますか?",
  "過去、5年以内に病気やケガで手術を受けたことまたは継続して7日以上の入院をしたことはありますか?",
];

const Questionnaire = ({ answers, setAnswers, isConfirm }) => {
  const handleAnswer = (answeredIndex, answer) => {
    setAnswers(answers.map((e, i) => (i === answeredIndex ? answer : e)));
  };
  return (
    <div>
      <FormControl component="fieldset">
        {answers
          .filter((_, i) => i === 0 || answers[i - 1])
          .map((answer, i) => (
            <React.Fragment key={i}>
              <FormLabel component="legend">{QUESTIONS[i]}</FormLabel>
              {isConfirm ? (
                <Typography>{answer === "yes" ? "はい" : "いいえ"}</Typography>
              ) : (
                <RadioGroup
                  row
                  aria-label="gender"
                  name="row-radio-buttons-group"
                  onChange={(_evt, value) => {
                    handleAnswer(i, value);
                  }}
                >
                  <FormControlLabel
                    value="yes"
                    control={<Radio />}
                    label="はい"
                  />
                  <FormControlLabel
                    value="no"
                    control={<Radio />}
                    label="いいえ"
                  />
                </RadioGroup>
              )}
            </React.Fragment>
          ))}
      </FormControl>
    </div>
  );
};

export default Questionnaire;

参考サイト

React 初心者が Material-UI で今どきの Web フォームを作ってみた(Stepper編)
ReactでRedux Formを使ってフォームの管理をしてみる
react -router-domの導入!Material UIのボタンを使って画面遷移をしよう!
入門者でもわかるReact Routerを利用したルーティング設定の基礎
Using Framer Motion to make page transitions in React
React Router | Learn routing in React with React-Router 6
こんにちはMUI! 新しくなったMaterial UI v5
React 初心者が Material-UI で今どきの Web フォームを作ってみた(react-hook-form編)
React hooksを基礎から理解する (useContext編)
こんなに簡単なの?React Hook useContextでデータ共有
Multi-Step Form Using React and Material-UI
How to Create a Multi-Step Form With React, React Hooks, Bootstrap and Firebase
React Js Multi-Step-Form Project

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0