Skip to content Skip to sidebar Skip to footer

SetInterval + React Hooks Causing Multiple Updates Within Component

I'm building a stopwatch UI that shows the time in seconds. With the click of a button, the timer will start counting upwards and stop when it is clicked again. User should be able

Solution 1:

This is a classic example of stale closure in React hooks, inside your setInterval value of time is not changing after calling setTime. Change your code with:

setInterval(() => setTime(currentTime => currentTime + 1), 1000).

setTime just like the setState of classful components also accepts a callback function which has the current value as the first param

Also, the timer variable is useless in you code since on every re-render it will be undefined and you wont't have the access of return value of setInterval, so it will reinitialize the setInterval. To handle that use useRef, you can store the return of setInterval in .current, which will be available to you after subsequent re renders so no more re-init of setInterval and you can also use clearInterval

Answer :

const {useState, useRef} = React;
const {render} = ReactDOM;

const Timer = () => {
  const [time, setTime] = useState(0);
  const timer = useRef(null);
  const startStopTimer = () => {
    if (!timer.current) {
      timer.current = setInterval(() => setTime(currentTime => currentTime + 1), 1000);
    } else {
      clearInterval(timer.current);
      timer.current = null;
    }
  };

  return (
    <div>
      <p>Time: {time} seconds</p>
      <button
        onClick={startStopTimer}
      >
        Start/Stop
      </button>
    </div>
  );
};

render(<Timer />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Solution 2:

Here is an example using a react class component. This example keeps track of the start time instead of adding to some value on a certain interval. Then when you stop the timer it accumulates the passed time.

The callback passed to setInterval might not always exactly be called each n ms. If the JavaScript engine is busy it might take a few ms longer. Keeping a counter would slowly offset the actual passed time the longer it runs.

const {Component} = React;
const {render} = ReactDOM;

class StopWatch extends Component {
  state = {startTime: null, accTime: 0, intervalId: null};
  
  componentWillUnmount() {
    clearInterval(this.state.intervalId);
  }
  
  ms() {
    const {startTime, accTime} = this.state;
    if (!startTime) return accTime;
    return Date.now() - startTime + accTime;
  }
  
  start = () => {
    this.setState({
      startTime: Date.now(),
      intervalId: setInterval(() => this.forceUpdate(), 10)
    });
  }
  
  stop = () => {
    clearInterval(this.state.intervalId);
    this.setState({
      startTime: null,
      accTime: this.ms(),
      intervalId: null
    });
  }
  
  reset = () => {
    this.setState({
      accTime: 0,
      startTime: this.state.startTime && Date.now()
    });
  }

  render() {
    return (
      <div>
        <h1>{this.ms() / 1000}</h1>
        {this.state.startTime
          ? <button onClick={this.stop}>stop</button>
          : <button onClick={this.start}>start</button>}
        <button onClick={this.reset}>reset</button>
      </div>
    );
  }
}

render(<StopWatch />, document.getElementById("stop-watch"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="stop-watch"></div>

Post a Comment for "SetInterval + React Hooks Causing Multiple Updates Within Component"