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.