[React] アンケートアプリで質問に回答したらページ再読み込みをせずに次の質問に進めるようにする

2021.07.27

こんにちは、CX事業本部 IoT事業部の若槻です。

Webアンケートやフォームの実装では、質問や回答方法の分かりにくさやレイテンシーなどユーザーの途中離脱につながる要素をなるべく無くすことが重要となります。

今回は、Reactで実装した1問ずつ質問が画面表示されるアンケートアプリで、質問に回答したらページ再読み込みをせずに次の質問に進めるようにしてみました。

環境

$ npm list --depth=0
...
├── react@17.0.2
├── typescript@4.2.4
...

コード

質問ページ(/question)のモジュールのコードです。

components/pages/QuestionPage.tsx

import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";

export interface Choices {
  [key: number]: string;
}

export interface Question {
  questionNumber: number;
  questionText: string;
  choices: Choices;
}

// 質問データ
const questions: Question[] = [
  {
    questionNumber: 1,
    questionText: "開発に使用している端末は?",
    choices: {
      1: "MacBook",
      2: "Surface",
      3: "ChromeBook",
    },
  },
  {
    questionNumber: 2,
    questionText: "よく使うコードエディターは?",
    choices: {
      1: "Visual Studio Code",
      2: "IntelliJ IDEA",
      3: "Microsoft Visual Studio",
      4: "その他",
    },
  },
  {
    questionNumber: 3,
    questionText: "興味のある職種は?",
    choices: {
      1: "エンジニア",
      2: "システムアーキテクチャ",
      3: "スクラムマスター",
      4: "テクニカルサポート",
      5: "その他",
    },
  },
  {
    questionNumber: 4,
    questionText: "得意なプログラム言語は?",
    choices: {
      1: "JavaScript",
      2: "Go",
      3: "Python",
      4: "Swift",
    },
  },
];

const useStyles = makeStyles((theme) => ({
  typography: {
    marginTop: theme.spacing(20),
    marginBottom: theme.spacing(10),
  },
  grid: {
    margin: "auto",
  },
  button: {
    marginTop: "auto",
    marginBottom: theme.spacing(5),
    width: "250px",
    height: "200px",
    fontSize: "30px",
    margin: theme.spacing(1),
    textTransform: "none",
  },
}));

const QuestionPage: React.FC = () => {
  const classes = useStyles();

  // 回答中の質問をStateで管理
  const [currentQuestion, setCurrentQuestion] = useState<Question>(
    questions[0]
  );

  // 質問の選択肢ボタンのクリックハンドラー
  const onClickHandler = () => {
    const currentNumber = currentQuestion.questionNumber;
    // 回答したのが最後の質問であれば完了画面に遷移
    if (currentNumber === questions.length) {
      window.location.href = "/thankyou";
    }
    // まだ質問が残っていれば次の質問を表示
    setCurrentQuestion(questions[currentNumber]);
  };

  return (
    <div>
      <Typography variant="h2" align="center" className={classes.typography}>
        <div>
          {"質問 " +
            currentQuestion.questionNumber +
            "/" +
            questions.length +
            ":" +
            currentQuestion.questionText}
        </div>
      </Typography>
      <Grid container>
        {(Object.keys(currentQuestion.choices) as unknown as number[]).map(
          (choice, index) => (
            <Grid item className={classes.grid}>
              <Button
                variant="contained"
                color="primary"
                className={classes.button}
                onClick={() => onClickHandler()}
              >
                {index + 1 + ". " + currentQuestion.choices[choice]}
              </Button>
            </Grid>
          )
        )}
      </Grid>
    </div>
  );
};

export default QuestionPage;

Reactのフック関数であるuseStateを使用することにより、質問データをstateとして管理し、質問に回答したらページの再読み込みや移動をすることなく次の質問に進めるようにしています。

動作

4問続く質問ページ(/question)で、質問に回答して次の質問に移動する際に、ページの再読み込みが起きずスムーズに次の質問に移動できています。

課題(解決済み)

元データではそんなことはないのに、ボタンの英字がなぜかすべて大文字になってしまっています。

これについては次回以降で解決策を確認してみます。

(追記)

下記の対応で解決しました。

元データで小文字の英字はちゃんと小文字のままとなりました。

参考

以上