Newbie here;
I have a <QuizPage /> component with a child component called <TriviaObject /> which holds three properties; a question, a correct answer with a string value, and an array of incorrect answers. a JSON sample of how the API returns the data model:
{
question: "question",
correct_answer: "answer",
incorrect_answers: [ "answer1", "answer2", "answer3", ... ]
The issue is, when I click over an answer, the whole array of answers shuffles on the screen. Alla I need to achieve is clicking on an answer and store it into a state, in order to submit the selection of answers and show how many were correct. Interesting fact, after pasting console.log almost everywhere, nothing returns on console which makes me wonder if either state or effect are working correctly.
quiz is my state array that renders a series of TriviaObject components, and the problem began once I have implemented a new state userSelection to store the user answer for each question.
So far this is what I have done:
export default function QuizPage() {
const [quiz, setQuiz] = useState([]);
// state to store user's selected answer for each question
const [userSelection, setUserSelection] = useState([])
useEffect(() => {
async function getData() {
try {
const res = await fetch("https://opentdb.com/api.php?amount=10")
const data = await res.json()
setQuiz(data.results)
} catch (error) {
console.error("Error fetching data:", error)
}
}
getData()
}, [])
if (!quiz || quiz.length === 0) {
return <div>Loading...</div>; // or any loading indicator
}
const handleAnswerSelected = (answer, index) => {
console.log(`Selected Answer for Question ${index + 1}:`, answer)
// update the userSelection state array with the selected answer
const updatedSelection = [...userSelection];
updatedSelection[index] = answer;
setUserSelection(updatedSelection);
console.log("Update userSelection:", updatedSelection)
}
const renderTrivia = quiz.map((object, index) => {
console.log("Rendering TriviaObject with props:", object)
return (
<TriviaObject
key={index}
question={decodeHTML(object.question)}
correct_answer={decodeHTML(object.correct_answer)}
incorrect_answers={object.incorrect_answers.map(item => decodeHTML(item))}
onAnswerSelected={(answer) => handleAnswerSelected(answer, index)}
/>
)})
return (
<div className="quiz-page">
{renderTrivia}
<button>Submit Answers</button>
</div>
)
}
Below, the child component <TriviaObject />. I have declared a state allAnswers to be populated by the mix of right and wrong answers fetched from the API, implemented a shuffle logic, and deconstructed the props object to take off the correct and incorrect answers, shuffle them, and allow me to access their index in order to store them in the userSelection state array. I have checked in the React Dev Tools Components and every time I click over an answer is correctly stored in the userSelection state array.
export default function TriviaObject(props) {
const { question, onAnswerSelected, correct_answer, incorrect_answers } = props;
const [allAnswers, setAllAnswers] = useState([]);
// Apply shuffle only once when the component mounts
useEffect(() => {
console.log("shuffling answers...");
const shuffledArray = shuffle([correct_answer, ...incorrect_answers]);
setAllAnswers(shuffledArray);
}, [correct_answer, incorrect_answers]);
// Fisher-Yates shuffle alrgorithm
function shuffle(array) {
const shuffledArray = [...array];
for (let i = shuffledArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = shuffledArray[i];
shuffledArray[i] = shuffledArray[j];
shuffledArray[j] = temp;
}
return shuffledArray;
}
return (
<div className="trivia-obj">
<h2>{question}</h2>
<ul>
{allAnswers.map((answer, index) => {
const styles = {
backgroundColor:
onAnswerSelected[index] === answer ? '#D6DBF5' : '' ,
border:
onAnswerSelected[index] === answer
? 'none'
: '1px solid #D6DBF5'
};
return (
<li
key={index}
onClick={() => onAnswerSelected(answer)}
style={styles}
>
{answer}
</li>
);
})}
</ul>
</div>
);
}
I have been looping over this issue for the past few days and I am going nowhere. I am on the learning stage of React Basics, so please, apologise me in advance if the code looks ugly but I genuinely wish to learn from any feedback of experienced React dev in here. Many thanks in advance for your time to read my request.
After initialising userSelection state array to store the selected answer for each question, I have created the handleAnswerSelected function which is fired up from a listener onAnswerSelected. Doing this, I can pass the listener prop to the child component, for which takes the selected answer and update the userSelection. I have tried implementing useMemo() hook to save both correct_answer and incorrect_answers in a variable and increase performance after shuffling this way:
export default function TriviaObject(props) {
const { question, onAnswerSelected } = props;
const { correct_answer, incorrect_answers } = useMemo(() => props, [props]); // Memoizing props
const memoizedAnswers = useMemo(() => [correct_answer, ...incorrect_answers], [correct_answer, incorrect_answers]);
const [allAnswers, setAllAnswers] = useState([])
return(
// JSX here
)
}
Although it improved the performance, It did not change the final result.