Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialSaud Tauqeer
Courses Plus Student 8,099 PointsHaving 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
33,778 PointsFirst, 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 :)
Masataka Miyahara
1,451 PointsI 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
2,965 PointsThank 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.
Saud Tauqeer
Courses Plus Student 8,099 PointsSaud Tauqeer
Courses Plus Student 8,099 PointsThank you i think i understand.