Handling answers
We need to save the selected answer as a new piece of state and make the reducer
handle the selection of an answer and destructure it from initialState:
const initialState = {
questions: [],
status: "loading",
index: 0,
answer: null,
};
function reducer(state, action) {
switch (action.type) {
case "dataReceived":
return { ...state, questions: action.payload, status: "ready" };
{/* more stuff */}
case "newAnswer":
return { ...state, answer: action.payload };
default:
throw new Error("Action unknown");
}
}
export default function App() {
const [{ questions, status, index, answer }, dispatch] = useReducer(
reducer,
initialState
);
{/* more stuff */}
}
The “newAnswer” action will dispatched in the Options component, again by passing
the dispatch function into it:
export default function App() {
{/* more stuff */}
return (
<div className="App">
<Header />
<Main>
{status === "loading" && <Loader />}
{status === "error" && <Error />}
{status === "ready" && (
<StartScreen numQuestions={numQuestions} dispatch={dispatch} />
)}
{status === "active" && (
<Question
question={questions[index]}
dispatch={dispatch}
answer={answer}
/>
)}
</Main>
</div>
);
}
receiving those props inside Question and passing them into Options:
export default function Question({ question, dispatch, answer }) {
console.log(question);
return (
<div>
<h4>{question.question}</h4>
<Options question={question} dispatch={dispatch} answer={answer} />
</div>
);
}
and here calling the dispatch function passing the index of each option:
export default function Options({ question, dispatch, answer }) {
return (
<div className="options">
{question.options.map((option, index) => (
<button
className="btn btn-option"
key={option}
onClick={() => dispatch({ type: "newAnswer", payload: index })}
>
{option}
</button>
))}
</div>
);
}
Then, we’ll use the answer state to conditionally assign another classname
to the selected answer button:
export default function Options({ question, dispatch, answer }) {
const hasAnswered = answer !== null;
return (
<div className="options">
{question.options.map((option, index) => (
<button
className={`btn btn-option ${index === answer ? "answer" : ""} ${
hasAnswered
? index === question.correctOption
? "correct"
: "wrong"
: ""
}`}
key={option}
disabled={hasAnswered}
onClick={() => dispatch({ type: "newAnswer", payload: index })}
>
{option}
</button>
))}
</div>
);
}
${index === answer ? "answer" : ""}styles the selected answer option${hasAnswered ? index === question.correctOption ? "correct" : "wrong" : ""}styles the correct and false answer options after an answer has been givendisabled={hasAnswered}disables the option buttons after an answer has been given
To display a progress score, we need another state variable:
const initialState = {
questions: [],
status: "loading",
index: 0,
answer: null,
points: 0,
};
The points are supposed to be updated upon the user giving an answer, so inside the
"newAnswer" case of the reducer function. Points should be added, if the
answer is correct only:
function reducer(state, action) {
{/* more stuff */}
case "newAnswer":
const question = state.questions.at(state.index);
return {
...state,
answer: action.payload,
points:
action.payload === question.correctOption
? state.points + 1
: state.points,
};
{/* more stuff */}
}
to validate, we first get the current question from the state (at current index)
we then check if the selected answer (
action.payload) is matching the correct answer (question.correctOption), in which case increasing the value by 1
Important
As shown above for the points, it is encouraged to put as much logic for
updating the state into the reducer function.