Reputation: 3521
I cannot understand. To cut it short, I have these 2 variables which are being used in the state
questionLoaded
gameOver
This component needs to wait until it finishes getting data using the function selectRandomQuestion
.
Therefore if i remove the questionLoaded
, it will give me exception when it's looping through the map.
Now I had to create another variable to check when the game is over to unrender this component, therefore the terniary condition questionLoaded || gameOver ?
I tried checking the variable currentQuestion
to conditionally render, but for some reason it will give exception when looping through the map in the options
array of this property. Which means currentQuestion
gets data first before it's own child property options
has any data when hitting the condition check
The problem is that this looks kind of dirty, I'm still using react just for a few weeks and I'm not sure if this is the appropriate way to deal with state updates or is there a better way to handle it.
To sum it up, my problem is when rendering components, I often need to wait for the state to update when the rendering depends on some conditions, and everytime I need a new condition that means I need another set of logic using another state variable. Which means creating another useEffect
with extra logic and to be triggered when that variable is updated.
import React, { useState, useEffect } from 'react';
import './GuessPicture.css';
import questions from './data/questions.js';
function GuessPicture() {
const [currentQuestion, setCurrentQuestion] = useState({});
const [unansweredQuestions, setUnansweredQuestions] = useState([]);
const [answeredQuestions, setAnsweredQuestions] = useState([]);
const [questionLoaded, setQuestionLoaded] = useState(false);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
setUnansweredQuestions(questions);
selectRandomQuestion();
setQuestionLoaded(true);
}, []);
useEffect(() => {
if (unansweredQuestions.length > 0) nextQuestion();
else alert('game over');
}, [unansweredQuestions]);
function selectRandomQuestion() {
const index = Math.floor(Math.random() * questions.length);
let selectedQuestion = questions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
function nextQuestion() {
const index = Math.floor(Math.random() * unansweredQuestions.length);
let selectedQuestion = unansweredQuestions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
// Fisher Yates Shuffle Algorithm
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const onClickOption = (event) => {
if (currentQuestion.correctValue == event.target.dataset.value) {
setAnsweredQuestions((answeredQuestions) => [
...answeredQuestions,
currentQuestion,
]);
const newUnansweredQuestions = unansweredQuestions.filter(
(item) => item.id != currentQuestion.id
);
setUnansweredQuestions(newUnansweredQuestions);
} else alert('Wrong');
};
return (
<>
{questionLoaded || gameOver ? (
<div className="guess-picture-container">
<div className="guess-picture">
<img src={currentQuestion.image} alt="English 4 Fun" />
</div>
<div className="guess-picture-answers-grid">
{currentQuestion.options.map((key) => (
<button
key={key.value}
onClick={onClickOption}
data-value={key.value}
>
{key.display}
</button>
))}
</div>
</div>
) : null}
</>
);
}
export default GuessPicture;
Upvotes: 1
Views: 278
Reputation: 412
Your code looks fine to me, however I would avoid the ternary operator in this situation and use the short-circuit operator instead. This makes your code a little cleaner.
instead of:
{questionLoaded || gameOver ? <SomeComponentHere /> : null}
try using:
{(questionLoaded || gameOver) && <SomeComponentHere />}
Why avoid the ternary operator? Because it's not a ternary operation, it is a boolean check. This makes your code more semantically appropriate.
Upvotes: 2