Search code examples
reactjsreact-hookstimersetinterval

How to update state using setInterval on functional components in React


I am trying to implement a countdown, but the state is not being update as expected. It stays stuck on initial value 30. I have no clue how to solve it. Can anyone help me please?

  const [timer, setTimer] = useState(30);

  function handleTimer() {
    const interval = setInterval(() => {
      setTimer((count) => count - 1);
      if (timer <= 0) {
        clearInterval(interval);
      }
    }, 1000);
  }

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

Solution

  • The problem is about javascript closures, you can read more about it here

    Also, Dan has a full detailed article talking about this specific problem. I strongly suggest you read it.

    And here is a quick solution and demonstration for your problem. First of all, the useEffect will be executed every time the component is remount. And this could happen in many different scenarios depending on your code. Hence, The useEffect starts fresh and closes on new data every time.

    So all we need is to save our values into ref so we can make use of the same reference every re-render.

    // Global Varibales
    const INITIAL_TIMER = 30;
    const TARGET_TIMER = 0;
    
    // Code refactoring
      const [timer, setTimer] = useState(INITIAL_TIMER);
      const interval = useRef();
    
      useEffect(() => {
        function handleTimer() {
          interval.current = setInterval(() => {
            setTimer((count) => count - 1);
          }, 1000);
        }
    
        if (timer <= TARGET_TIMER && interval.current) {
          clearInterval(interval.current);
        }
        if (timer === INITIAL_TIMER) {
          handleTimer();
        }
      }, [timer]);