clearInterval in React
Asked Answered
D

8

39

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;
Defamation answered 24/8, 2017 at 13:17 Comment(1)
Possible duplicate of Is it really necessary to clear the timers before unmounting the component?Intermixture
K
50

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)
}
Kayak answered 27/10, 2017 at 7:51 Comment(1)
You should not use states when you don't need a rerender. Use useRef instead. It will hold the value through the re-reders but won't trigger one.Changeless
L
26

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)
    })
},[])
Lavalava answered 18/11, 2020 at 11:32 Comment(0)
S
15

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

Simplistic answered 24/8, 2017 at 13:23 Comment(0)
M
8

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);
Mayhap answered 31/10, 2022 at 18:55 Comment(1)
Thanks I was looking for a way to clear timeout on click of button, and not only on unmount, was missing implementing useCallback hookCoffeng
S
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);
Shamefaced answered 24/8, 2017 at 13:22 Comment(1)
How would you produce a method to start and stop outside of useEffect?Photophore
S
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;
Safier answered 31/8, 2022 at 13:48 Comment(0)
B
0

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.

Barnebas answered 21/9, 2023 at 10:5 Comment(0)
S
-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)
  }
Statutory answered 13/11, 2021 at 7:3 Comment(1)
Why you've declared an object with let? How is that even possible?Allahabad

© 2022 - 2024 — McMap. All rights reserved.