I've created a small quiz app that has a countdown timer. It currently resets when navigated to the next question. However, it is resetting the countdown on first click of the "next question" button before an answer is selected and resets at any point during the countdown. How can I keep the timer from resetting when clicking the "next question" button and before an option is selected?
function App() {
let [points, setPoints] = useState(null);
let [counter, setCounter] = useState(null);
let [question, setQuestions] = useState();
let [is_started, setStart] = useState(false);
let [isCorrect, setIsCorrect] = useState(false);
let [hasAnswered, setHasAnswered] = useState();
let [isChecked, setChecked] = useState(null);
let [showAnswer, setShowAnswer] = useState(false);
let [is_end, setEnd] = useState(false);
let [selectionMade, setSelectionMade] = useState();
const getData = () => {
fetch("./questions.json")
.then(function (response) {
return response.json();
})
.then(function (data) {
setQuestions(data);
});
};
useEffect(() => {
getData();
}, []);
function Countdown({ seconds }) {
const [timeLeft, setTimeLeft] = useState(seconds);
const intervalRef = useRef;
useEffect(() => {
intervalRef.current = setInterval(() => {
setTimeLeft((t) => t - 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
useEffect(() => {
if (timeLeft <= 0) {
clearInterval(intervalRef.current);
document.getElementById("false").checked = true;
nextQuestion();
}
}, [timeLeft]);
return <div>{timeLeft}s left to answer</div>;
}
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
type="radio"
name="answer_group"
className="answer"
id={`${props.correct}`}
value={props.answer}
onChange={checkAnswer}
checked={isChecked === props.answer}
disabled={hasAnswered === true ? true : false}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
ans.correct === true ? setIsCorrect(true) : setIsCorrect(false);
setHasAnswered(true);
setChecked(ans.value);
}
function Starter(props) {
return (
<button className="" onClick={props.start}>
{is_end ? "Restart" : "Start"}
</button>
);
}
function Quiz(props) {
return (
<div className="quiz-questions">
<p className="question" id="questions">
{props.question}
</p>
<ul className="answers" role="radiogroup" aria-labelledby="questions">
{props.children}
</ul>
</div>
);
}
function start() {
setCounter(0);
setPoints(0);
setStart(!is_started);
setEnd(false);
}
function nextQuestion() {
setSelectionMade(true);
setIsCorrect(false);
setHasAnswered(false);
setChecked(null);
setShowAnswer(false);
if (document.querySelector('input[name="answer_group"]:checked') == null) {
setSelectionMade(false);
return;
}
let val = document.querySelector('input[name="answer_group"]:checked')
.value;
let answerObj = question[counter].answers.filter(
(ans) => ans.value === val
)[0];
let updated_points = answerObj.correct ? points 1 : points;
setPoints(updated_points);
let nextQuestion = counter 1;
if (counter < question.length - 1) {
setCounter(nextQuestion);
} else {
let today = new Date();
let dd = String(today.getDate()).padStart(2, "0");
let mm = String(today.getMonth() 1).padStart(2, "0");
let yyyy = today.getFullYear();
let hours = today.getHours();
let AmOrPm = hours >= 12 ? "pm" : "am";
hours = hours % 12 || 12;
let minutes = "" today.getMinutes();
let minutesFixed = minutes < 10 ? "0" minutes : minutes;
let finalTime = hours ":" minutesFixed " " AmOrPm;
let date = mm "/" dd "/" yyyy " at " finalTime;
setEnd(!is_end);
setStart(!is_started);
setCounter(0);
console.log({ date: date, score: updated_points });
setTopScores([...topScores, { date: date, score: updated_points }]);
}
}
return (
<>
<div
className="Quiz slide-top"
style={{ backgroundColor: "rgba(255,255,255,.5)" }}
>
{selectionMade === false && hasAnswered === false ? (
<div className="notification swing-in-top-fwd">
<p>Please select an answer to continue</p>
</div>
) : (
""
)}
{!is_started ? (
<div className="quiz-intro">
<div className="start-intro-wrapper">
<Starter start={start} />
</div>
</div>
) : (
<div className="quick-wrapper slide-in-bottom">
{!isChecked && <Countdown seconds={15} />}
<div className="status-details"></div>
<Quiz question={question[counter].question}>
<ul>
{question[counter].answers.map((answer, index, arr) => {
return (
<Answer
key={index}
index={index}
answer={answer.value}
correct={answer.correct}
/>
);
})}
</ul>
</Quiz>
<div className="answer-controls">
<div className="answer-buttons">
<button
className="button"
onClick={(e) => nextQuestion()}
>
{isChecked === null ? "Select an answer" : "Next question"}
</button>
</div>
</div>
</div>
)}
</div>
</>
);
}
export default App;
CodePudding user response:
I would display buttons conditionally and remove onClick
if isChecked
is null
.
<div className="answer-buttons">
{isChecked === null ?
<button className="">Select an answer</button>
:
<button className="" onClick={(e) => nextQuestion()}>
Next question
</button>
}
</div>
CodePudding user response:
You have a few issues to fix here... the main problem is that you're defining components inside components - so each time App
renders (e.g. because of a state change), it will recreate a new Countdown
component, which will reset the timer.
If you pull the countdown out to its own component -
const Countdown = ({ seconds, onNextQuestion }) => {
const [timeLeft, setTimeLeft] = useState(seconds);
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => {
setTimeLeft((t) => t - 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
useEffect(() => {
if (timeLeft <= 0) {
clearInterval(intervalRef.current);
document.getElementById("false").checked = true;
onNextQuestion();
}
}, [timeLeft, onNextQuestion]);
return <div>{timeLeft}s left to answer</div>;
}
And then wire that in:
<Countdown seconds={15} onNextQuestion={nextQuestion} />
Then it will fix the problem of it resetting when you hit "next". You should ideally do that with all the parts of your App
that could stand-aone.
Here's an updated CodeSandbox showing the solution above.
CodePudding user response:
I have updated several parts of your code. Please have a look here. Hope this would be helpful for you.