I am working on a react project and I have a counter. I have added the functionality so that the counter will reset when it reaches 0 but I also want to have the functionality if the button is clicked.
I have tried doing it but the results are something different.
This is what happens when the countdown reaches 0 and resets to 10 again:

but this is what happens when i click the button it resets the countdown to 10 but the countdown is somewhat different:

here is my code:
const [counter, setCounter] = React.useState(10);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
if (counter === 0) {
setCurrentQuestion(currentQuestion 1);
setCounter(20);
}
}, [counter]);
here is the code to reset the counter on button click:
const handleAnswerOptionClick = (isCorrect) => {
const nextQuestion = currentQuestion 1;
if (nextQuestion < questions.length) {
setCurrentQuestion(nextQuestion);
setCounter(10);
} else {
setShowScore(true);
}
};
and here is the html for it:
<div className="answer_div">
{questions[currentQuestion].answerOptions.map(
(answerOption) => (
<button
onClick={() =>
handleAnswerOptionClick(
answerOption.isCorrect
)
}
>
{answerOption.answerText}
</button>
)
)}
</div>
how can i make the counter behave normally on button click.
CodePudding user response:
I created a sandbox that shows one correct way to handle your problem.
Why your code is not working?
Because every time you force set the counter with the setCounter(10); you are triggering the useEffect. The problem is that timeout you triggered in the previous call to useEffect is still running. So you are effectively running 2 (or more) useEffect concurrently.
Every time you call useEffect you need to check if there is a pending timeout and eventually clear it.
const timerRef = useRef();
useEffect(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
if (counter > 0) {
timerRef.current = setTimeout(() => setCounter(counter - 1), 1000);
}
if (counter === 0) {
setCurrentQuestion(currentQuestion 1);
setCounter(10);
}
}, [counter]);
In this example I am storing the reference of every timeout I create in a React's ref. And then when I enter again in useEffect I check if there is any pending timeout and clear it. This way I can now set a new timer that will be the only one running.
To give you a full explanation, the timeouts in your code will show the wrong numbers because every timeout call will use their version of the counter state. The one in the moment when the useEffect that created the timeout was run! So you are actually see values from the past :D.
CodePudding user response:
we need a sandbox however, try using setInterval instead , also consider adding a cleanup function to your use effect, also your state update is depending on the previous state , consider using the function patter setCounter((state)=> state-1)
React.useEffect(() => {
const interval = setInterval(()=>{
setCounter((state)=>{
if(state>0){
return state-1
}
});
// cleanup
return ()=> clearInterval(interval);
},1000)
}, [counter]);



