I am trying to create a timer in react, using setTimeout and useState.
The runTimer function shown below is responsible for keeping track of time and it is supposed to stop calling itself if the state variable timerRunning is false.
If I click on the Start button and later on the Stop button, I see this does not work and the counter continues to increase.
As I logged the values of timmerRunning and seconds from the body of the App component and from the runTimer function, I noticed they are different. The value of timerRunning in App is different from the value of timerRunning in runTimer, and the same is true for seconds.
Code:
import "./styles.css";
import { useState } from "react";
export default function App() {
const [timerRunning, setTimerRunning] = useState(false);
const [seconds, setSeconds] = useState(0);
console.log("App body --------------");
console.log({ timerRunning, seconds });
function runTimer() {
console.log("*********** runTimer **********");
console.log({ timerRunning, seconds });
if (timerRunning) {
setTimeout(() => {
setSeconds((prev) => prev + 1);
runTimer();
}, 1000);
}
}
const startTimer = () => {
console.log("startTimer");
setTimerRunning(true);
runTimer();
};
const stopTimer = () => {
// Complete this function
console.log("stopTimer");
setTimerRunning(false);
};
const resetTimer = () => {
// Complete this function
console.log("resetTimer");
setTimerRunning(false);
setSeconds(0);
};
return (
<div className="container">
<h1>Timer</h1>
<span> 0 mins </span>
<span> {seconds} secs</span>
<div>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
<button onClick={resetTimer}>Reset</button>
</div>
</div>
);
}
Here is an excerpt from the console:
index.js:27 App body --------------
index.js:27 {timerRunning: true, seconds: 9}
index.js:27 *********** runTimer **********
index.js:27 {timerRunning: true, seconds: 0}
index.js:27 App body --------------
index.js:27 {timerRunning: true, seconds: 10}
index.js:27 App body --------------
index.js:27 {timerRunning: true, seconds: 10}
index.js:27 stopTimer
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 10}
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 10}
index.js:27 *********** runTimer **********
index.js:27 {timerRunning: true, seconds: 0}
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 11}
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 11}
index.js:27 *********** runTimer **********
index.js:27 {timerRunning: true, seconds: 0}
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 12}
index.js:27 App body --------------
index.js:27 {timerRunning: false, seconds: 12}
As you can see I clicked on Stop and triggered stopTimer after 10 seconds. After that App sees timerRunning as false, whereas runTimer sees it as true. This causes runTimer to keep actively calling setTimeout.
See a sandbox here: https://codesandbox.io/s/hooks-usestate-timer-q-forked-issue-with-state-variable-f9i0eq?file=/src/App.js:0-1231
Why does runTimer have a separate instance of seconds and timerRunning? What is the proper way of implementing a timer?