Search code examples
javascriptreactjssettimeoutuse-effect

React Countdown Reset


I'm trying to build a countdown timer but I'm facing an issue which I don't understand. When I press start the timer works fine and the same goes when I pause it.

The problem is when i press the reset button, which is supposed to stop the countdown and reset it to its original value(at the moment 25).

When I press it the countdown stops, it goes to 25 but then it goes to the previous number. For instance if I press it at 16, it stops, goes to 25 and then back at 16 or 15.

I guess it has something to do with useEffect since handleReset updates both counter and runnning but I'm not really sure why. If anyone could explain to me the reason that would be great.

Here's App.js

const App = () => {
  const [counter, setCounter] = useState(25);
  const [running, setRunning] = useState(false);

  const handlePlayPause = () => {
    running ? setRunning(false) : setRunning(true);
  };

  const handleReset = () => {
    setRunning(false);
    setCounter(25);
  };

  useEffect(() => {
    if (running) {
      counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
    }
  }, [counter, running]);

  return (
    <div className="container">
      <h1>Pomodoro Timer</h1>
      <div className="set-timer">
        <Break />
        <Session />
      </div>
      <Display counter={counter} />
      <Controller handlePlayPause={handlePlayPause} handleReset={handleReset} />
    </div>
  );
};

And the full code here: https://codepen.io/mugg84/pen/yLepaKm?editors=0010

Thanks for your help!


Solution

  • Few things here I'd probably change.

    First off, handleReset shouldn't probably set running to false as the counter is still running. You only reset the value.

    Then, to fix the issues I'd suggest using setInterval and you need to use clearInterval in combination with useRef. The logic can be simplified, but this is more readable I guess.

    const handleReset = () => {
        // stop on reset
        clearInterval(timer.current) 
        timer.current = undefined;
        
        setCounter(25);
      };
    
    const timer = React.useRef(undefined);
      
    
    React.useEffect(() => {
        if (running) {
          if (!timer.current) {
            timer.current = setInterval(() => setCounter(prevCounter => prevCounter - 1), 1000);
          } else if(counter == 0 && timer.current) {
            // stop if reached 0
            clearInterval(timer.current) 
            timer.current = undefined;
          }
        } else {
          // stop if turned off
          clearInterval(timer.current)
          timer.current = undefined;
        }
      }, [counter, running]);