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

9 Answers 9

51

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)
}
Sign up to request clarification or add additional context in comments.

1 Comment

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.
26

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)
    })
},[])

Comments

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

Comments

9

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

1 Comment

Thanks I was looking for a way to clear timeout on click of button, and not only on unmount, was missing implementing useCallback hook
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);

1 Comment

How would you produce a method to start and stop outside of useEffect?
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;

Comments

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.

Comments

0
import React from "react";
import "./styles.scss";
import "bootstrap/dist/css/bootstrap.min.css";
import { Container } from "react-bootstrap";

let stopWatchID: any;
export default class App extends React.Component<any, any> {
  constructor(props: any) {
    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() {
    stopWatchID = setInterval(() => {
      this.setState({ time: this.state.time + 1 });
    }, 1000);
  }

  // Stop the stopwatch
  componentWillUnmount() {
    clearInterval(stopWatchID);
  }

  // Reset the stopwatch
  resetHandler() {
    this.setState({
      time: 0,
    });
    this.componentWillUnmount();
  }

  getSeconds(time: any) {
    return `0${time % 60}`.slice(-2);
  }

  getMinutes(time: any) {
    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>
    );
  }
}

Comments

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

1 Comment

Why you've declared an object with let? How is that even possible?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.