skip to Main Content

Even after adding if else statment inside setInterval() function to clear the interval once my time variable reaches 0, the setInterval() is still running with negative time value.

Please specify what the problem is and provide some solution for the same.

Here is my code for count-down timer component:

import { useEffect, useState, useRef } from "react";

export const Timer = () => {
  const [time, setTime] = useState(10000);
  const [pause, setPause] = useState(false);
  let timerRef = useRef(null);

  useEffect(() => {  
    startTimer();
  
    return () => clearInterval(timerRef.current);
  }, []);

  const stopTimer = () => {
    clearInterval(timerRef.current);
    setPause(true);
  };

  const startTimer = () => {
    setPause(false);
    timerRef.current = setInterval(() => {
      if (time <= 0) {
        clearInterval(timerRef.current);
        setPause(true);
      }
      else{
        setTime((prevTime) => prevTime - 1000);
      }
    }, 1000);

  };

  const getFormattedText = (time) => {
    const SECONDS = 1000;
    const MINUTES = 60 * SECONDS;
    const HOURS = 60 * MINUTES;
    const DAYS = 24 * HOURS;

    const days = Math.floor(time / DAYS);
    const hours = Math.floor((time % DAYS) / HOURS);
    const minutes = Math.floor((time % HOURS) / MINUTES);
    const seconds = Math.floor((time % MINUTES) / SECONDS);

    return (
      <div className="countdown-timer">
        {days < 10 ? `0${days}` : days} : {hours < 10 ? `0${hours}` : hours} :{" "}
        {minutes < 10 ? `0${minutes}` : minutes} :{" "}
        {seconds < 10 ? `0${seconds}` : seconds}
      </div>
    );
  };
  return (
    <>
      {getFormattedText(time)}
      <div>
        <button onClick={pause ? startTimer : stopTimer}>
          {!pause ? "Stop" : "Start"} Timer
        </button>
      </div>
    </>
  );
};

export default Timer;

Here is the output after 0 second:

This is the link for Output’s image

3

Answers


  1. you click event is likely automatically firing

    <button onClick={() => {pause ? startTimer : stopTimer}}>
    
    Login or Signup to reply.
  2. The setInterval inside your startTimer is receiving only a static value of time – the initial 10000 stays as it is. Even if you setTime, the updated time is only reflected outside the setInterval and not within, because setInterval executes on its own scope and context. So the else part is executed always, giving you negative values.

    To fix, you will need a useEffect that will respond to the change due to setTime Replace your startTimer method with the below code,

    useEffect(() => {
      console.log(time);
      if (time <= 0) {
        clearInterval(timerRef.current);
        setPause(true);
      }
    }, [time]);
    
    const startTimer = () => {
      if (time > 0) {
        setPause(false);
        timerRef.current = setInterval(() => {
          setTime((prevTime) => prevTime - 1000);
        }, 1000);
      }
    };
    

    your initial startTimer callback is split into using useEffect wherein it can listen and respond to change in state time due to setTime. Please refer https://react.dev/reference/react/useEffect#updating-state-based-on-previous-state-from-an-effect for further refinement of the solution.

    Login or Signup to reply.
  3. The problem lies in the closure created for the time state inside the
    setInterval() callback. When your setInterval() function is set up, it
    captures the value of time at that moment, and subsequent updates to
    time aren’t reflected within that closure.

    const startTimer = () => {
      setPause(false);
      timerRef.current = setInterval(() => {
        setTime((prevTime) => {
          if (prevTime <= 1000) { 
            clearInterval(timerRef.current);
            setPause(true);
            return 0; 
          }
          return prevTime - 1000;
        });
      }, 1000);
    };
    

    The current state of time is always checked against 0 correctly,
    preventing it from going negative. The interval is cleared correctly
    when the time reaches 0 or less. The setPause(true) action is
    correctly called within the context where prevTime is guaranteed to be
    the latest value of time.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search