36

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;
Lucas
  • 361
  • 1
  • 3
  • 3
  • 1
    Possible duplicate of [Is it really necessary to clear the timers before unmounting the component?](https://stackoverflow.com/questions/45158702/is-it-really-necessary-to-clear-the-timers-before-unmounting-the-component) – Shubham Khatri Aug 24 '17 at 13:37

7 Answers7

48

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)
}
Dinesh Patil
  • 1,042
  • 10
  • 13
23

You can use setTimeout 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)
    })
},[])
Majid Parvin
  • 4,499
  • 5
  • 29
  • 47
14

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);

Daniel Andrei
  • 2,654
  • 15
  • 16
7

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);
Murat Karagöz
  • 35,401
  • 16
  • 78
  • 107
4

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);
Henry Woody
  • 14,024
  • 7
  • 39
  • 56
1

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;
HarshitRV
  • 13
  • 5
-1

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)
  }
Sammy Aguns
  • 97
  • 1
  • 2