javascriptreactjsreact-hookssetinterval

Why does consoling prints the same value but state gets updated when used inside useEffect - React


In a video I saw creating a progress bar and updated its value using state and wrapped it inside setInterval for updating it every 15 millisecond and the whole being wrapped inside useEffect, with no dependency. As state change triggers a UI update, the component re-renders and state value changes, consoling the value should print from 3000 till 0 (or in short in descending order) but it only prints 3000 (200 times) why?

const [progressBar, setProgressBar] = useState(3000);

useEffect(() => {
  const progress = setInterval(() => {
    console.log(progressBar);
    setProgressBar((prevVal) => prevVal - 15);
  }, 15);

  return () => clearInterval(progress);
}, []);

If there is no dependency means the contents inside useEffect should be executed only once know? Kindly explain.


Solution

  • If there is no dependency means the contents inside useEffect should be executed only once know?

    Yes, the effect ran once and instantiated a callback function that is called on an interval with a set Javascript Closure.

    The interval is why the callback is called repeatedly, and the closure is why you see the same progressBar value each time. The reason the setProgressBar call works is because it is working from a separate closure that is passed the current state value, e.g. the callback passed to setProgressBar.

    If you would like to see the state update you can use a separate effect with a dependency on the state value that is updating.

    const [progressBar, setProgressBar] = useState(3000);
    
    // Run once to instantiate interval callback to enqueue state updates
    useEffect(() => {
      const progress = setInterval(() => {
        setProgressBar((prevVal) => prevVal - 15);
      }, 15);
    
      return () => clearInterval(progress);
    }, []);
    
    // Run each time `progressBar` state updates
    useEffect(() => {
      console.log(progressBar);
    }, [progressBar]);