SetInterval + React Hooks Causing Multiple Updates Within Component
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"