Search code examples
reactjsuse-stateclearinterval

clearInterval not working if using setState React.js


I'm building a countdown timer with React and encountered this: clearInterval works onCklick if it doesn't update the component. If I update state within the interval function clearInterval doesn't clear the interval anymore. I understand that it has to do with the component re-rendering? Here is the relevant part of the code. Advice is much appreciated:

const [sessionCount, setSessionCount] = useState(25);
const [timeLeft, setTimeLeft] = useState("25:00")

  
var timer ;
 
function startHandle() {
    let endTime = (sessionCount * 60000) + +new Date();
    timer = setInterval(function() {
        let diff = endTime - +new Date();
        const min = Math.floor(diff / 60000);
        const sec = Math.floor((diff / 1000) % 60);
        setTimeLeft(min + ":" + sec) //if this line is removed, then clearInterval works
    }, 1000)
};
function resetHandle() {
  setTimeLeft("25:00");
  clearInterval(timer);
};
<div id="time-display-container">
    <div id="timer-label">Session</div>
    <time id="time-left"></time>
    <button id="start_stop" onClick={startHandle}>start/stop</button>
    <button id="reset" onClick={resetHandle}>reset</button>
</div>

Solution

  • Move the timer outside the App component.

    import React, { useState } from "react";
    
    let timer;
    
    
    export default function App() {
      const [sessionCount, setSessionCount] = useState(25);
      const [timeLeft, setTimeLeft] = useState("25:00");
    
    
      function startHandle() {
        let endTime = sessionCount * 60000 + +new Date();
        timer = setInterval(function () {
          let diff = endTime - +new Date();
          const min = Math.floor(diff / 60000);
          const sec = Math.floor((diff / 1000) % 60);
          setTimeLeft(min + ":" + sec); //if this line is removed, then clearInterval works
        }, 1000);
      }
    
      function resetHandle() {
        setTimeLeft("25:00");
        clearInterval(timer);
      }
    
      return (
        <div id="time-display-container">
          <div id="timer-label">Session</div>
          <time id="time-left" />
          {timeLeft}
          <button id="start_stop" onClick={startHandle}>
            start/stop
          </button>
          <button id="reset" onClick={resetHandle}>
            reset
          </button>
        </div>
      );
    }
    
    

    also, you check for the timer value to prevent multiple set intervals to be set. I guess defining this timer in useEffect and calling them in some other state change would be more reactive, but the above code is a fix that worked for me in codesandbox