简体   繁体   中英

How to pause a setInterval countdown timer in react?

I'm trying to build a pomodoro timer with a pause option. There's an analogue clock and a digital timer. My issue is with the digital timer - I can pause it by clearing the interval but do not know how to resume it without starting from the top (with a new setInterval).

This is the codesandbox of the project .

This is the relevant part from the DigitalClock component:

const timer = () => {
    const now = Date.now()
    const then = now + mode.duration * 60 * 1000
    countdown = setInterval(() => { // That's how I resume it (with a re-render)
        const secondsLeft = Math.round((then - Date.now()) / 1000)
        if(secondsLeft < 0 || pause) {
            clearInterval(countdown) // That's how I pause it (by clearing the interval)
            return;
        }
        displayTimeLeft(secondsLeft)
    }, 1000)
}

I think pausing the timer could be done with a boolean instead of clearing the interval, So let's say that u have also a boolean keeping track of if it's paused on top level

let paused = false;

and you should consider looking up for if timer is not paused then do the math inside so

countdown = setInterval(() => { // That's how I resume it (with a re-render)
    if(!paused) {
        const secondsLeft = Math.round((then - Date.now()) / 1000)
        if(secondsLeft < 0 || pause) {
            clearInterval(countdown) // That's how I pause it (by clearing the interval)
            return;
        }
        displayTimeLeft(secondsLeft)
    }
}, 1000)

The only thing that's left is to toggle this paused boolean to true/false when someone click's the Pause button.

I don't know about React that much but that would be the choice I would go if i was doing this task:)

Possible solution - don't clear the interval when it is paused, just don't update the secondsLeft on the tick

Also, secondsLeft can be an integer, it doesn't have to be related to the actual time.

 // global variables var pause = false; var elapsed, secondsLeft = 60; const timer = () => { // setInterval for every second countdown = setInterval(() => { // if allowed time is used up, clear interval if (secondsLeft < 0) { clearInterval(countdown) return; } // if paused, record elapsed time and return if (pause === true) { elapsed = secondsLeft; return; } // decrement seconds left secondsLeft--; displayTimeLeft(secondsLeft) }, 1000) } timer(); const displayTimeLeft = (seconds) => { document.getElementById("time").textContent = seconds; } document.getElementById("pause").addEventListener("click", (evt) => { pause =;pause. evt.target?textContent = pause: "resume"; "pause"; if (pause === false) { secondsLeft = elapsed; } });
 <div id="time"></div> <button id="pause">pause</button>

Using react, the timer should be in a useEffect hook (assuming you're using functional components). The useInterval will be placed inside and will run naturally within, updating the display through the useEffect hook. Place the clear interval in the useEffect return statement so when the timer expires, the interval will clear itself.

Then using pause as a state variable, manage your timer with buttons.


const [seconds, setSeconds] = useState(30);
const [pause, setPause] = useState(false);

useEffect(() => {
  const interval = setInterval(() => {
    if(!pause) { //I used '!paused' because I set pause initially to false. 
      if (seconds > 0) {
        setSeconds(seconds - 1);
      }
    }
  }, 1000);
  return () => clearInterval(interval);
});

const handlePauseToggle = () => {
  setPause(!pause);
}

Add a button to click, and your pause feature is set up.

*** Side note, feel free to ignore*** It looks like you have a way to display the time already, but I think it would be easier if you use the 'seconds' state variable in curly brackets to display your timer instead of creating a function (see below).


<div>
  <p>0:{seconds >= 10 ? {seconds} : `0${seconds}`}</p>
</div>

This will display the timer correctly in a single line. Minutes is easy to add and so on.

Just a thought to make your code easier.

Using custom hook

Follow the following steps:

  • Create a new state variable to store the counter value on resume
  • On start check if you have a resume value then use it otherwise use the initial counter

Here's the full custom hook:

const useCountdown = ({ initialCounter, callback }) => {
  const _initialCounter = initialCounter ?? DEFAULT_TIME_IN_SECONDS,
    [resume, setResume] = useState(0),
    [counter, setCounter] = useState(_initialCounter),
    initial = useRef(_initialCounter),
    intervalRef = useRef(null),
    [isPause, setIsPause] = useState(false),
    isStopBtnDisabled = counter === 0,
    isPauseBtnDisabled = isPause || counter === 0,
    isResumeBtnDisabled = !isPause;

  const stopCounter = useCallback(() => {
    clearInterval(intervalRef.current);
    setCounter(0);
    setIsPause(false);
  }, []);

  const startCounter = useCallback(
    (seconds = initial.current) => {
      intervalRef.current = setInterval(() => {
        const newCounter = seconds--;
        if (newCounter >= 0) {
          setCounter(newCounter);
          callback && callback(newCounter);
        } else {
          stopCounter();
        }
      }, 1000);
    },
    [stopCounter]
  );

  const pauseCounter = () => {
    setResume(counter);
    setIsPause(true);
    clearInterval(intervalRef.current);
  };

  const resumeCounter = () => {
    startCounter(resume - 1);
    setResume(0);
    setIsPause(false);
  };

  const resetCounter = useCallback(() => {
    if (intervalRef.current) {
      stopCounter();
    }
    setCounter(initial.current);
    startCounter(initial.current - 1);
  }, [startCounter, stopCounter]);

  useEffect(() => {
    resetCounter();
  }, [resetCounter]);

  useEffect(() => {
    return () => {
      stopCounter();
    };
  }, [stopCounter]);

  return [
    counter,
    resetCounter,
    stopCounter,
    pauseCounter,
    resumeCounter,
    isStopBtnDisabled,
    isPauseBtnDisabled,
    isResumeBtnDisabled,
  ];
};

And here's an example of using it on codepen: React useCountdown

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM