🙌

React Redux を初めて使って簡単なクイズアプリを作ってみる

2021/03/25に公開

React をある程度触ってみて State が複数のコンポーネント間で相互に作用しあう場合に便利な Redux というライブラリを使えるようになりたいと思い、Redux を使ったシンプルなアプリを作りました。

とりあえず、json形式でクイズの問題のデータが必要なので、questionsにデータを格納します。今回、私は別のURLからフェッチしていますが別に直にjsonを書いてquestionsにぶち込んでも Redux を使う上では変わりません。

App.js
import './App.css';
import {useEffect, useState} from 'react';
import axios from 'axios';
import QuestionsWrapper from './components/questionsWrapper';
import { Provider } from 'react-redux';
import store from './redux/store';

function App() {

  const [questions, setQuestions] = useState();

  useEffect(() => {
    axios.get(`URL/to/get/questions`)
    .then(res => {
      const qdata = res.data;
      setQuestions(qdata);
    })
  }, [questions])

  return (
    <div className="App">
      <Provider store={store}>
        <QuestionsWrapper questions={questions}></QuestionsWrapper>
      </Provider>
    </div>
  );
}

Redux を使うには

import { Provider } from 'react-redux';

でProviderをインポートして、return の中でReduxでステートを管理したい部分をProviderでラッピングします。そのうえで、Provider コンポーネントに store を props として渡します。この store は別の store.js ファイルを作成しそれを最初にインポートしてあります。store.js は 今回はたったの3行です。

redux/store.js
import { createStore } from "redux";
import rootReducer from "./reducers";

export default createStore(rootReducer);

store.js は名前の通り redux の状態管理が行われる store を管理します。reducers ディレクトリにある複数のリデューサーファイルを reducers ディレクトリ内の index.jsで管理しエクスポートしています。

reducers/index.js
import { combineReducers } from "redux";
import quiz from "./quiz";

export default combineReducers({ quiz });

quiz.js が今回は唯一のリデューサーです。2つのアクションがあり、1つはjavascript の array でクイズのそれぞれの問題の正解不正解を管理し、もう1つは正解数を数えるアクションです。(もし redux を利用しない場合は questionsWrapper コンポーネント内に複数の question コンポーネントがあるため、2つのコンポーネント間でコミュニケーションをとる必要があり、ちょっと面倒くさかったです。まあ言っても言うほどの手間でもないですが笑)

reducers/quiz.js
import { COUNT_CORRECT, SET_CORRECT} from "../actionTypes";

const initialState = {
    correctCount: 0,
    correctArray: [],
};

const todos =  (state = initialState, action) => {
  switch (action.type) {
    case COUNT_CORRECT: {
        return {
            ...state,
            correctCount: state.correctArray.filter(Boolean).length
          };
    }
    case SET_CORRECT:{
        var tmp = state.correctArray;
        tmp[action.payload.qnum - 1] = action.payload.cor;
        return {
            ...state,
            correctArray: tmp,
        }
    }
    default:
      return state;
  }
}

export default quiz;

ちなみに、actionTypes.js 内ではシンプルにアクションの名前をまとめてあります。

actionTypes.js
export const COUNT_CORRECT = "COUNT_CORRECT";
export const SET_CORRECT = "SET_CORRECT";

最後に、question コンポーネントで json 形式で管理されているクイズの問題をいい感じにパースして、html と css で見た目を調整すればシンプルなクイズアプリの完成です。

question.js
import React, {useState} from 'react';
import {useDispatch} from 'react-redux';

export default function Question(props){

    const [selected, setSelected] = useState("Choose");
    const [questionStyle, setQuestionStyle] = useState('question');
    const [answerClass, setAnswerClass] = useState('answer')

    const dispatch = useDispatch();

    var answerChoicesItems = (props.answerChoices !== undefined) ? props.answerChoices.map((choice) =>
    <option key={choice} value={choice}>{choice}</option>   
    ) : [];
    answerChoicesItems.unshift(<option key="" value="" >Choose</option>);

    if(props.showAnswer === true &&  answerClass !== 'showAnswer'){
        setAnswerClass('showAnswer');
        var tmpqs = questionStyle.split(' ');
        if (!tmpqs.includes('showAnswer')){
            tmpqs.push('showAnswer');
        }
        setQuestionStyle(tmpqs.join(' '));
    }

    function handleChange(e){
        setSelected(e.target.value);
        var qnum = props.qnumber;
        var cor = false;
        var tmpqs = questionStyle.split(' ');
        if(props.answer.answerString === e.target.value){
            tmpqs.push('correct')
            if (tmpqs.includes('wrong')){
                tmpqs = tmpqs.filter(e => e !== 'wrong')
            }
            cor = true;
        }else{
            tmpqs.push('wrong');
            if (tmpqs.includes('correct')){
                tmpqs = tmpqs.filter(e => e !== 'correct');
            }
            cor = false;
        }
        var qs = tmpqs.join(' ')
        setQuestionStyle(qs);
        dispatch({type:"SET_CORRECT", payload: {cor: cor, qnum:qnum}});
    }

    return <div className={questionStyle} >
        <div>Question {props.qnumber}</div>
        <div>{props.qquestion}</div>
        <select value={selected} onChange={handleChange}>
            {answerChoicesItems}
        </select>
        <div className={answerClass}>Answer: {props.answer.answerString}</div>
    </div>
}

SET_CORRECT アクションはユーザーの答えが変更されるたびに実行され、正解か不正解を判定しています。(まあ、毎回実行しなくても提出時にまとめて正解不正解を判定すればいいのですが、たいして負荷がかかるわけでもないですしリアルタイムで変化が見れたほうがなんかいいなと思ったのでそうしました笑)
(クイズを提出して答え合わせがされた後に答えの画像を表示するようなこともしていましたが、redux とは関係ないのでその部分はコードから切り取ってあります。css 等の見た目に関する部分も同様にカットしました。)

redux を初めて使ってみて、今回のようなシンプルなアプリの場合は手間としては redux を使った場合と使わなかった場合では大して変わらなかったのですが、おそらくアプリが大きくなればコンポーネント間のコミュニケーションをとることが多くなると思うので、そこで redux が便利になるのではないかと思います。機会があれば redux を使ってちょっと規模を大きくしたアプリを作ってみるかもしれません笑 もし欠けている部分で知りたい部分があれば教えてください!(この記事のメインの redux に関連するものを特に載せているためです。)

ここでこのエントリーは以上です。呼んでいただきありがとうございました!

Discussion