Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

JavaScript React Components Stateful Components and Lifecycle Methods Update the Stopwatch State with componentDidMount()

Having trouble understanding logic.

import React, { Component } from 'react';

class Stopwatch extends Component {
    state = {
        isRunning: false,
        elapsedTime: 0,
        previousTime: 0
    };

    componentDidMount(){
        this.intervalID = setInterval(()=> this.tick(), 100)
    }

    componentWillUnmount() {
        clearInterval(this.intervalID);
    }

    tick = () =>{
        if (this.state.isRunning){
            const now = Date.now();
            this.setState((prevState)=>({
                previousTime: now,
                elapsedTime: prevState.elapsedTime + (now - 
                this.state.previousTime)
            }));
        }
    }

    handleStopwatch = () => {
        this.setState(( prevState ) =>({ 
            isRunning: !prevState.isRunning
        }));
        if (!this.isRunning){
            this.setState({
                previousTime: Date.now()
            });
        }
    }
    handleReset = () => {
        this.setState({
            elapsedTime: 0

        });

    }

    render(){
        const seconds =  Math.floor( this.state.elapsedTime / 1000 );

        return (
            <div className= "stopwatch" >
                <h2>Stopwatch</h2>
                <span className="stopwatch-time">
                    { seconds }
                </span>
                <button onClick={ this.handleStopwatch }>
                    { this.state.isRunning ? 'Stop' : 'Start' }
                </button>
                <button onClick={ this.handleReset } >Reset</button>
            </div>
        );
    }

}







//can someone explain the working of this function
// the if statement part. we are passing date.now() 
 handleStopwatch = () => {
        this.setState(( prevState ) =>({ 
            isRunning: !prevState.isRunning
        }));
        if (!this.isRunning){
            this.setState({
                previousTime: Date.now()
            });
        }
    }

// here also we are updating the same state.
  tick = () =>{
        if (this.state.isRunning){
            const now = Date.now();
            this.setState((prevState)=>({
                previousTime: now,
                elapsedTime: prevState.elapsedTime + (now - 
                this.state.previousTime)
            }));
        }
    }

2 Answers

Gabbie Metheny
Gabbie Metheny
33,773 Points

First, I see an error in your handleStopwatch method: you should be checking for !this.state.isRunning, not !this.isRunning, since isRunning lives in the Stopwatch's state.

As to the logic, we're checking to see if the Stopwatch is not running because the handler runs at the point when "Start" or "Stop" is clicked. When you click "Start," isRunning will still be false, so the if block will execute. The previousTime will be updated to the exact moment that code ran, or the moment the stopwatch started.

Now that handleStopwatch has been called and isRunning has been set to true, the if block inside tick will execute, at the interval provided in componentDidMount (100ms). We save the exact time tick runs inside a variable, now, so that we can reference it in two different places in the code that follows. I think it helps to think about elapsedTime first, since it uses the existing value of previousTime before it's updated. So the elapsed time should be about 100ms more than the last value of elapsed time, but because of browser throttling with setInterval, we need to subtract the time that the stopwatch last updated from the current time before adding it to the elapsed time.

So ideally our count looks like: 100ms > 200ms > 300ms > 400ms etc.

But actually it probably looks something like: 100ms > 220ms > 320ms > 430ms etc.

So we're basically saying, if the stopwatch updated at 100ms (previousTime), and now it's 220ms (now), we should add the difference between those (120ms) to the old elapsedTime to get the new true elapsedTime. Additionally, we're updating previousTime to the current value of now, or Date.now() (like we did in handleStopwatch's if block) so that we can run the same calculation again in roughly 100ms.

Hope that made sense, let me know if there's anything I could clarify :)

Thank you i think i understand.

Masataka Miyahara
Masataka Miyahara
1,450 Points

I was confused about the same methods and seemed like I understood.

I think the most confusing point is that the setState method in React is asynchronous. From my perspective, that is why the following codes behave the same way*.

This code

handleStopwatch = () => {
  if (!this.state.isRunning) {
    this.setState({
      previousTime: Date.now()
    })
  }
  this.setState(prevState => ({
    isRunning: !prevState.isRunning
  }))
}

will behave the same way as the following code.

handleStopwatch = () => {
  this.setState(prevState => ({
    isRunning: !prevState.isRunning
  }))
  if (!this.state.isRunning) {
    this.setState({
      previousTime: Date.now()
    })
  }
}

In each case, if the user clicks the "start" button, !this.state.isRunning is evaluated as true because setState is asynchronous and this.setState(prevState => ({isRunning: !prevState.isRunning})) has not set isRunning to true yet.

* Strictly speaking, the two codes above differ from each other in terms of the the timing of adding the setState function to JavaScript Engine's stack.

I do not feel very confident of my understanding, so please let me know if there is anything wrong with my understanding:)

Zaberca David
Zaberca David
2,964 Points

Thank you for your response. It helped me research further and understand the concept. It seems that since setState is an async function, and async function are put into event loop and always executed after the call stack is cleared, it is guaranteed the this.state.isRunning will be false for the entirety of the handleStopWatch.