Search code examples
reactjssetinterval

React self increment timer


I wanted to start a timer when the page loads, and my code is like following:

  function App() {
     const [sec, setSec] = useState(0);

     useEffect(()=>{
      setInterval(()=>{setSec(prev=>prev+1)
      }, 1000)}, [sec])

  return (
    <span>{sec}</span>
  )}

But the counter increments not just every second but very fast. I didnt know what the issue is.

I think the setInterval() will run every second, which updates the sec variable, which in turn would calls the callback function in useEffect() so it can run again.

I did find some example code for setting a react timer online but they use useRef() to hold a setInterval() callback function and then update the sec variable like setSec(sec+1). I loosely understand the logic but not completely, and I can't figure out why my code does not work.

Thank you so much if anyone can help me. Sorry if it is some simple thing that I missed.


Solution

  • I see two problems.

    First, remove sec from the dependency array on useEffect. You should use an interval or an effect dependency, but not both. When you use both you're essentially creating a new interval on every re-render, so every re-render will increase the amount of increments and the rate will compound indefinitely.

    If you want to rely on the dependency array and the effect re-running on every render, you'd just use setTimeout instead of setInterval. For example:

    useEffect(() => {
      setTimeout(() => {
        setSec((prev) => prev + 1);
      }, 1000);
    }, [sec]);
    

    That way each re-render of the component (and each re-invoking of the effect) only performs one increment, not a new ongoing interval of incremenets.


    Second, and more subtly, the effect is leaving a side-effect that isn't cleaned up. This will be evident in a development environment if the component technically renders twice, and you'd see your counter increment by 2 instead of 1 every second.

    You can "clean up" an effect by returning a cleanup function from the useEffect callback. React would use that function when the component unloads. For example:

    useEffect(() => {
      const x = setInterval(() => {
        setSec((prev) => prev + 1);
      }, 1000);
    
      // return a cleanup function here
      return () => clearInterval(x);
    }, []);