In this lesson we'll build a stopwatch component that maintains its own state. We'll start by creating the static UI, then take the dynamic parts and accept them as props. After that we'll refactor that to state and add event handlers to update the state.
[00:00] Let's get started by making a function called stopwatch. The stopwatch function is going to return our UI. That UI is just going to be a div with a label. That label, we'll hard code it to zero milliseconds. We'll update that later.
[00:18] We'll add a button -- actually, add two buttons. The first one is going to say, "Start," and the second will say, "Clear." Then, we'll go ahead and render that instead of this empty div, and we get the initial output. I'm going to go ahead and copy and paste some styles here, so that it looks nice. All right, that looks a lot better.
[00:37] Now, I'm going to take away this hard-coded 0ms, and we're going to change that to lapse. We'll pull the lapse from our props, lapse. We also need to have this start to be dynamic as well. We'll say, "Running," and if it's running, this will say, "Stop." Otherwise, it'll say, "Start." We'll get running from our props as well.
[01:02] Let's pass those props here. We'll say running is true, and lapse is zero. We'll get stop there. If we change this to false, then we get start. If we change the lapse to 10, then we get 10 milliseconds. Perfect.
[01:17] Doing things this way makes it a lot easier to add dynamic capabilities to the existing markup that we've created, because we've been able to extract the specific parts of the state that are in our render method here. That makes it easier for us to take these things and move them into state.
[01:34] The next thing we're going to do is we're going to make a class called stopwatch. That extends react.component. Every component has a render method, so I'll put that here. That render method is going to have all the same contents that our original function component had.
[01:52] We'll get rid of the stopwatch, and instead of props being passed to our function, we're going to get to get it from this.props. Now, everything works exactly the same way as it did before. We're going to change this to true, and we're going to get stop. We can change this to 100. We're going to get 100 milliseconds.
[02:12] Next, let's go ahead and move this to state. We'll say state equals lapse of zero and running of false. That's our initial state. Instead of pulling these things from props, we'll just pull them from state. Now, we have our initialization there.
[02:28] We'll change that back to 100 milliseconds or 10 milliseconds. That's all wired up properly. We'll say true, here. That changes the stop. We're getting things from our stopwatch component. We can also remove these props.
[02:42] Now, we need to be able to make this dynamic. We need to dynamically update the state as we go. Let's go ahead and make these buttons functional. We'll add an on-click handler to our start and stop button. This will be this.handleRunClick.
[02:55] We'll create that member property on our stopwatch instances with handleRunClick equals this arrow function. We'll alert you clicked to make sure that things are wired up properly.
[03:10] Let's go and update the state. We'll say this.setState, and we'll set lapse to 10, and running to true. If I click on that, that sets the state. Perfect.
[03:24] Let's go ahead and wire up the handleClearClick really quick. We'll go back down here on our clear button, and we'll say onclick=this.handleClearClick. We'll take that, and we'll do something similar up here, with handleClearClick equals an arrow function. We say this.setState, where lapse is zero and running is false.
[03:48] If I click start, and then clear, all those things are wired up together properly. Let's go ahead and start the timer. We'll say const start time=date.now, and we'll subtract that from this.state.lapse. Then we'll say setInterval, and we'll leave this blank. That's going to be zero.
[04:09] Every millisecond, or as soon as it possibly can, we'll call this.setState with a new lapse, lapse of date.now minus the start time. Outside of that interval, we'll also say this.setState running is true.
[04:31] Now, that works, and we can get it to run. Actually, every time I click, it's setting a new set interval, which itself will call setState. This thing is calling setState over and over really, really fast. Let's go ahead and make it not do that.
[04:45] We actually need to change the behavior based off of the running state. To do this, we're going to say this.setState, and provide an updater function, because we need to know what the running state is.
[04:56] We're going to say give me the state for this updater function, and then I can return the running state, running as notTheState.running. Based off of state.running, I know whether to set the interval or clear the interval.
[05:12] Let's go ahead. We'll get rid of this. We'll move this up. If it is running, we actually want to do the setInterval inside of the ellipse. If it is running, we need to clear the existing interval.
[05:26] We need to keep a handle on the interval identifier. We're going to say this.timer=setInterval. If it already is running, then we're going to say clearInterval this.timer. Otherwise, we'll go ahead and get the start time, and set the state. Let's save that. We'll start and stop, and start and stop. Then we can clear.
[05:50] But that clear button isn't working. The problem is that we're not clearing the interval. Let's go ahead and fix that also. When we clear this, we're going to basically do the same thing that we did here, so we'll just copy and paste that. If I start, and then clear, and start and stop and clear, then everything is working awesomely.
[06:11] We're going to review the way that we made our stopwatch component stateful. We started out by making a static render method that just rendered statically the information that we wanted to have rendered.
[06:21] That made it easier for us to extract the pieces that are stateful, like the lapse and running, here. We accepted those as props to make sure that those were wired up properly.
[06:32] We moved those props to state, and then, instead of pulling those from props, we pulled them from state. We added some interactivity. We added this on-click here, on-click here, and added the logic for those things here.
[06:48] To update the state, you use setState, and if you need to reference some existing state as you're updating the state here, then you use an updater function that accepts our state and returns the new state. Otherwise, you can simply call setState with an object if your new state doesn't depend on some old state.
[07:05] This implementation actually has a memory leak in it, but I'll fix that in another lesson.
Hi! So if you look closely at the logic, we set the start time once you click the start button and then reference that in the setInterval call. So it's set once each time you click start and never changes.
Hi! So this the code that i have written for the above practical
class StopWatch extends React.Component{ state = {lapse: 0, isRunning: false}
handleStartClick(){ this.setState({lapse: 10, isRunning: true})
} handleClearClick(){
}
render(){ const {lapse, isRunning} = this.state; return ( <div> <label>{lapse}ms</label> <button onClick={this.handleStartClick}>{isRunning ? 'Stop' : 'Start'}</button> <button onClick={this.handleClearClick}>Clear</button> </div> ) } }
ReactDOM.render(<StopWatch />,document.getElementById('root'))
getting this error on clicking 'start' button
Uncaught TypeError: Cannot read property 'setState' of undefined
and the error is completeley expected because wrong referencing of 'this'
but hows your prac working?
Change your implementation to this:
class StopWatch extends React.Component{
state = {lapse: 0, isRunning: false}
handleStartClick = () => {
this.setState({lapse: 10, isRunning: true})
}
handleClearClick = () => {
}
render(){
const {lapse, isRunning} = this.state;
return (
<div>
<label>{lapse}ms</label>
<button onClick={this.handleStartClick}>{isRunning ? 'Stop' : 'Start'}</button>
<button onClick={this.handleClearClick}>Clear</button>
</div>
)
}
}
ReactDOM.render(<StopWatch />,document.getElementById('root'))
See this video for an explanation about why.
Good luck!
This example code behaves weirdly when run in Firefox on my box. To fix it, I had to pass in a delay parameter to the call to setInterval like so:
this.timer = setInterval(() => {
this.setState({
lapse: Date.now() - startTime,})
}, 100)
I ran into the same problem as Cornelius on Firefox. It looks like Firefox doesn't work correctly if you don't provide a delay to setInterval. I just explicitly set it to 0 and it fixed the problem.
Hi Kent, thank you for this series of videos. I am trying to implement it in a React project, but it doesn't work. It says syntaxError: Unexpected token =
I am compiling with webpack and babel-loader Thank you
Hi mattia,
The public class fields syntax (state = {}
) is currently a stage-3 proposal in the EcmaScript standardization process of the TC39. This means that it wouldn't be included if you're using babel-preset-env
and you'll need to include the babel-plugin-transform-class-properties
transform for it in your babel configuration. If you're using create-react-app, it's included by default.
Learn more about the feature from this lesson.
Thanks for the course. Another question about the public class fields. Instead of
handleClearClick = () => {
You could have done this:
handleRunClick() {...}...
The public class field will create a new instance of the methods with each class instantiation, whereas the regular class method is the same thing for each class instance.
The regular class methods would require you to bind the handleRunClick and handleClearClick to this.
Why did you use the public class fields and what do you think are the benefits over the regular class methods?
Wow! You totally explained it in a way that couldn't have been more helpful in lesson 12.
https://egghead.io/lessons/egghead-use-class-components-with-react
PS: Looking back at this discussion, I now see you already mentioned that.
Yeah, the order of these particular lessons was a little tricky 😅
What IDE are you using? I noticed yours has intellisense.
I'm using atom
Hi Kent,
I know this is a bit off-topic but can you explain how this.timer
in handleClearClick
is able to reference the timer from handleRunClick
?
Thanks! Melissa.
this
represents the object which is our instance of the stopwatch component. All the methods there have access to that object and the properties on it. So when handleRunClick
sets this.timer
, because handleClearClick
has access to the same this
object it can access this.timer
:)
Hello Kent, What are you using to make the browser dynamically reflect changes as you type - considering we're updating a plain '.html' file, and don't have a build system like webpack running. Thanks.
Hi Kent, awesome course. I'm new to both JS and React. Just wanting to check with you that in the "Use Component State with React" lecture, the "startTime" variable is just a placeholder variable changes in value all the time? Ie, it does not record the actual start time of when I click the start button and later when I click the stop button, the start time has actually "moved". Thanks, Adam