Reputation: 391
I'm new at React and I was trying to create a simple stopwatch with a start and stop buttons. I'm banging my head against the wall to try to clearInterval with an onClick event on Stop button. I would declare a variable for the setInterval and then would clear it using the clearInterval. Unfortunately it is not working. Any tips? Thank you in advance.
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {time:0}
this.startHandler = this.startHandler.bind(this);
}
getSeconds(time){
return `0${time%60}`.slice(-2);
}
getMinutes(time){
return Math.floor(time/60);
}
startHandler() {
setInterval(()=>{
this.setState({time:this.state.time + 1});
},1000)
}
stopHandler() {
//HOW TO CLEAR INTERVAL HERE????
}
render () {
return (
<div>
<h1>{this.getMinutes(this.state.time)}:{this.getSeconds(this.state.time)}</h1>
<button onClick = {this.startHandler}>START</button>
<button onClick = {this.stopHandler}>STOP</button>
<button>RESET</button>
</div>
);
}
}
export default App;
Upvotes: 39
Views: 87195
Reputation: 331
You can use setInterval
inside useEffect
with no dependency so it calls once when the component is initiated, then call the clearInterval
when the component is unmounted.
useEffect(() => {
let intervalId = setInterval(executingFunction,1000)
return(() => {
clearInterval(intervalId)
})
},[])
Upvotes: 26
Reputation: 743
For those using functional react components, you can also use useIntervalEffect
custom hook from react-hookz library. Read more about this and other hooks from their official documentation.
Upvotes: 0
Reputation: 15732
For React 16.8+ with hooks you can store the intervalID in a ref value (rather than in state) since the component does not need to rerender when the intervalID updates (and to always have access to the most recent intervalID).
Here's an example:
function Timer() {
const [time, setTime] = React.useState(0);
const intervalIDRef = React.useRef(null);
const startTimer = React.useCallback(() => {
intervalIDRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
}, []);
const stopTimer = React.useCallback(() => {
clearInterval(intervalIDRef.current);
intervalIDRef.current = null;
}, []);
// resetTimer works similarly to stopTimer but also calls `setTime(0)`
React.useEffect(() => {
return () => clearInterval(intervalIDRef.current); // to clean up on unmount
}, []);
return (
<div>
<span>Time: {time}</span>
<button onClick={startTimer}>START</button>
<button onClick={stopTimer}>STOP</button>
</div>
)
}
Note that for a timer component like this, it's a better idea to update the time by referencing the current time (with performance.now
or new Date
) relative to the last updated time than to increment a time variable since setInterval
does not provide an accurate way of recording time and small inaccuracies will build up over time. You can check the timing with this script:
let lastTime = performance.now();
setInterval(() => {
const currentTime = performance.now();
console.log(currentTime - lastTime);
lastTime = currentTime;
}, 1000);
Upvotes: 9
Reputation: 1072
you can add interval to your component's state and can clear it whenever you want.
componentDidMount(){
const intervalId = setInterval(this.yourFunction, 1000)
this.setState({ intervalId })
}
componentWillUnmount(){
clearInterval(this.state.intervalId)
}
Upvotes: 51
Reputation: 13
componentWillUnmount() will do the trick for stopping as well as resetting the stopwatch. You can find more on this on react docs
import React, { Component } from 'react';
class StopWatch extends Component {
constructor(props){
super(props);
this.state = {
time : 0
}
this.startHandler = this.startHandler.bind(this);
this.resetHandler = this.resetHandler.bind(this);
this.componentWillUnmount = this.componentWillUnmount.bind(this);
}
// Start the stopwatch
startHandler() {
this.stopWatchID = setInterval(()=>{
this.setState({time:this.state.time + 1});
},1000);
}
// Stop the stopwatch
componentWillUnmount() {
clearInterval(this.stopWatchID);
}
// Reset the stopwatch
resetHandler(){
this.setState({
time: 0
})
this.componentWillUnmount();
}
getSeconds(time){
return `0${time%60}`.slice(-2);
}
getMinutes(time){
return Math.floor(time/60);
}
render () {
return (
<div>
<h1>{this.getMinutes(this.state.time)}:{this.getSeconds(this.state.time)}</h1>
<button onClick = {this.startHandler}>START</button>
<button onClick = {this.componentWillUnmount}>STOP</button>
<button onClick = {this.resetHandler} >RESET</button>
</div>
);
}
}
export default StopWatch;
Upvotes: 1
Reputation: 133
Create an ID for the timer, then Change your start startHandler and stopHandler as below;
let this.intervalID;
startHandler() {
this.intervalID = setInterval(()=>{
this.setState({time:this.state.time + 1});
},1000)
}
stopHandler() {
clearInterval(intervalID)
}
Upvotes: -1
Reputation: 2684
In your startHandler function you can do :
this.myInterval = setInterval(()=>{
this.setState({ time: this.state.time + 1 });
}, 1000);
and in your stopInterval() you would do clearInterval(this.myInterval);
Upvotes: 15
Reputation: 37624
You can use clearInterval(id)
to stop it. You have to store the id of the setInterval
e.g.
const id = setInterval(() = > {
this.setState({
time: this.state.time + 1
});
}, 1000)
clearInterval(id);
Upvotes: 7