I am working on a Quiz app. Consists of a question and four options per question. I want to change the background color of either of the options when the user clicks on it i.e. when user clicks on one answer, it has a background color, when another option is selected, inside the same question and the same list of options, make the newly selected option have the color of the previous option and revert previous option back to its original color.
What is happening now is that all the questions and their options are being affected by any one question. If I click on one option under one question, the options in other questions also change. And I found out the options in each question switch position everytime I click on one option.
Anybody has a clue what’s wrong?
Thanks.
Code Sandbox link is this: https://codesandbox.io/s/pedantic-julien-dlqwk8?file=/src/App.js
Code for the Quiz component below
import { useEffect, useState } from "react";
export default function Quiz() {
const [quiz, setQuiz] = useState(null);
const [playAgain, setPlayAgain] = useState(false);
const [togglePlayAgain, setTogglePlayAgain] = useState(playAgain);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [userAnswer, setUserAnswer] = useState(() => []);
const [showAnswer, setShowAnswer] = useState(false);
const [showAnswerBtn, setShowAnswerBtn] = useState(true);
const [colorToggle, setColorToggle] = useState(false);
const [colorObj, setColorObj] = useState({
backgroundColor: "",
});
const [isClickedIndex, setIsClickedIndex] = useState(-1);
// This hook fetches data once
// Added error handling to prevent errors filling up the UI
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=18&difficulty=hard&type=multiple")
.then(result => {
if (!result.ok) {
throw new Error("This is an HTTP error", result.status);
}
else {
return result.json();
}
})
.then(data => {
setQuiz(data.results);
console.log("Quiz data stored", data);
console.log(quiz);
return quiz && console.log("This is quiz data", quiz);
})
.catch(error => {
console.error("An error occurred!", error);
setQuiz(null);
setError(true);
})
.finally(() => {
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [togglePlayAgain])
function createRandomOptions(arr) {
let copyOptions = [...arr];
let randomOptionsArr = [];
while (copyOptions.length > 0) {
let randomIndex = Math.floor(Math.random() * copyOptions.length);
randomOptionsArr.push(copyOptions[randomIndex]);
copyOptions.splice(randomIndex, 1);
}
return randomOptionsArr;
}
function handleClick(index) {
setIsClickedIndex(index);
}
// Checks if the clicked option is the correct one and also checks if it was already picked before and prevents it from being added to the userAnswer array
function checkEachAnswer(option, correctAnswer, optIndex) {
// handleClick is called here with the value of the index of the options
handleClick(optIndex);
if (option === correctAnswer) {
console.log("Correct");
// setColorToggle(true);
// Check if clicked answer exists before and reset the value back to what was clicked to eliminate same answer repeated in the userAnswer array
if (userAnswer.includes(option)) {
let userAnsArrCopy = [...userAnswer];
let index = userAnsArrCopy.findIndex(elem => elem);
userAnsArrCopy[index] = option;
setUserAnswer(prevValue => {
return userAnsArrCopy;
});
}
else {
setUserAnswer(prevValue => {
return [...prevValue, option];
});
}
}
else {
console.log(option, "is incorrect", );
// setColorToggle(false);
}
}
const quizElements = quiz && quiz.map(eachQuiz => {
const incorrectOptions = eachQuiz.incorrect_answers;
const correctOption = quiz && eachQuiz.correct_answer;
let options = [];
options = incorrectOptions.concat(correctOption);
let randomOptions = createRandomOptions(options);
return (
<>
<div className="quiz-wrapper">
<p className="question">{eachQuiz.question}</p>
<ul>
{quiz && randomOptions.map((option, index) =>
{
return (
<li
key={index}
className={`option ${isClickedIndex >= 0} ? "active" : "null"`}
onClick={() => checkEachAnswer(option, correctOption, index)}
>
{option}
</li>
)
})
}
</ul>
</div>
</>
)
});
console.log(userAnswer);
// Displays the answers when we click on the Check Again button
function displayAnswer() {
setShowAnswer(true);
setPlayAgain(true);
setShowAnswerBtn(false);
}
// Responsible for the Play Again button
function updatePlayAgain() {
setTogglePlayAgain(!togglePlayAgain);
setPlayAgain(false);
setShowAnswer(false);
setShowAnswerBtn(true);
setUserAnswer([]);
}
return (
<>
{loading && <h3>Currently loading...</h3>}
{error && <h3>An error occurred while fetching data!</h3>}
{quiz && <h1 className="topic">Topic: Computer Science</h1>}
{quiz && quizElements}
{showAnswer && <p>You scored {userAnswer.length} / {quiz.length}</p>}
{quiz && showAnswerBtn &&
<button
onClick={() => displayAnswer()}
className="main-btn"
>
Check Answer
</button>
}
{quiz && playAgain &&
<button
onClick={() => updatePlayAgain()}
className="main-btn"
>
Play Again
</button>
}
</>
)
}
2
Answers
Finally figured it out. It was because I was using the
isClickedIndex
as the state for tracking all of the questions' options so any change to one is reflected on the others. I also moved the callbacks in thequizElements
variable into theuseEffect
so that I can set the values once per render and won't have to call them again as I was doing with the former code. This prevents the options from switching positions when clicked.Full and updated code:
You can use CSS:
option:checked { color: red; }
and you can specify styling of selected option with the:checked
tag as well.