Home > Net >  Using setinterval with updating a state in a functional component
Using setinterval with updating a state in a functional component

Time:01-18

The following code demonstrates a react functional component that has a single state variable named time. It has a button click to start which fires a function named updateTimer. This meant to move the timer from 0 to 1 to 2 to 3 and so on.

function timer() {

       const [time, updateTime] = useState(0);
       
       function updateTimer() {
           setInterval(() => {
              updateTime(time   1)
           },1000)
      }
}

return (
      <>
        <span>{time} seconds</span>
        <button onClick={updateTimer}>Click To Start</button>
      </>
)

But what happens is that the timer stops after 1. Apparently, the value of time does not get updated. Could someone please explain this?

CodePudding user response:

To update the state depending on the previous state use:

updateTime((prevTime) => prevTime   1)

CodePudding user response:

Because updateTime updates the value of time in the next render of the component, the time known to the setInterval callback is not updated and remained the initial value 0, and resets to 1 at every interval.

In a useEffect run the setter updateTime based on previous value to correct the counting, also cleanup and recreate the interval as needed when the counting starts or stops.

For a basic example, the following useEffect starts an interval when updating is set true by the button. The return runs when updating changes or when the component unmounted for cleaning up the interval, before a new one can be recreated.

  useEffect(() => {
    if (!updating) return;
    const count = setInterval(() => {
      updateTime((prev) => prev   1);
    }, 1000);
    return () => {
      clearInterval(count);
    };
  }, [updating]);

updating is added to the dependencies array so that useEffect listens to the changes of this state, and creates a new interval if it is true.

The setter updateTime does not change and it sets time based on the previous value internally, so updateTime and time do not need to be included in the dependencies array.

Example in full snippet:

const { useState, useEffect, Fragment } = React;

function Timer() {
  const [time, updateTime] = useState(0);
  const [updating, setUpdating] = useState(false);

  useEffect(() => {
    if (!updating) return;
    const count = setInterval(() => {
      updateTime((prev) => prev   1);
    }, 1000);
    return () => {
      clearInterval(count);
    };
  }, [updating]);

  return (
    <Fragment>
      <p>{time} seconds</p>
      <button onClick={() => setUpdating((prev) => !prev)}>{`Click To ${
        updating ? "Stop" : "Start"
      }`}</button>
    </Fragment>
  );
}

const App = () => {
  return (
    <div className="App">
      <Timer />
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));
.App {
  font-size: x-large;
}

button {
  padding: 9px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

  • Related