Search code examples
javascriptreactjstimerreact-hookscounter

Timer/Counter for react component - value remains 0 after increasing it with setInterval()


export default function Timer() {
   const [timer, setTimer] = useState(0)

   const checkTimer = () => {
     console.log(timer);
   }

   useEffect(() => {
      const timer = setInterval(() => {
        setTimer(prevCount => prevCount + 1);
      }, 1000);

      startProgram(); //This starts some other functions

      return () => {
        checkTimer();
        clearInterval(timer);
      }
   }, [])
}

Above is a simplified version of my code and the main issue - I am trying to increase the timer state by setting an interval in useEffect() (only once). However, in checkTimer() the value is always 0, even though the console statement execute every second. I am new to reactjs and would appreciate some help as this is already taking me too many hours to fix.


Solution

  • checkTimer is showing you the initial value of timer state because of stale closure. That means at the time when useEffect was executed, (i.e. once at component mount due to [] as dependency), it registered a cleanup function which created a closure around checkTimer function (and everything, state or props values, it uses). And when this closure was created the value of timer state was 0. And it will always remain that.

    There are few options to fix it.

    One quick solution would be to use useRef:

    const timer = useRef(0);
    
    const checkTimer = () => {
      console.log(timer.current);
    };
    
    useEffect(() => {
      const id = setInterval(() => {
        timer.current++;
      }, 1000);
    
      return () => {
        checkTimer();
        clearInterval(id);
      };
    }, []);
    

    Check this related post to see more code examples to fix this.


    Edit:

    And, if you want to show the timer at UI as well, we need to use state as we know that "ref" data won't update at UI. So, the option 2 is to use "updater" form of setTimer to read the latest state data in checkTimer function:

    const [timer, setTimer] = useState(0);
    
    const checkTimer = () => {
      let prevTimer = 0;
      setTimer((prev) => {
        prevTimer = prev; // HERE
        return prev; // Returning same value won't change state data and won't causes a re-render
      });
      console.log(prevTimer);
    };
    
    useEffect(() => {
      const id = setInterval(() => {
        setTimer((prev) => prev + 1);
      }, 1000);
    
      return () => {
        checkTimer();
        clearInterval(id);
      };
    }, []);