1.3 - Stop Watch App
This next time app we're going to make is going to be a stopwatch!
App Setup
To start we need to create a StopWatchApp.js file inside of our timer-apps folder, your timer-apps should look like the following.
Let's go ahead and get our StopWatchApp.js
file started with the following code:
Next, to get this to display, we need to change our parent component, TimePiecesApp to no longer have StopWatchApp commented out, so go ahead and make sure that your TimePiecesApp file looks like this.
Now, you should see your Stopwatch App h1, and we're ready to start developing the stopwatch.
App Planning
To start thinking about our stop watch app, we're going to want to consider what pieces of data we'll need to display for our stopwatch to work, and what kind of data we want in our state. When starting a React application, taking time to think through and carefully design the state will save you time and headache later.
We need a few capabilities for a stop watch, one the ability to keep track of time starting at a specified moment, two, different lap times need to be able to be recorded, and three we need to know whether or not the stopwatch is currently running. Our state needs to include these things so that we can easily display them to the screen.
Constructor
Go ahead and start a constructor for this component, keeping in mind the state that we need. We're also going to bind our methods to this in the constructor. Type out the following code in your constructor.
Notice here that we also are binding the methods to this
. The event handler has its context bound to the component instance. You can access props and state and call setState
or forceUpdate
from the bound handler. So in order to access setState
from event handlers, they need to be bound to the this
. So we can either bind the methods that need access to setState, or we can make them as arrow functions, since arrow functions do not bind their this, and therefore in them, you will still have access to setState.
In this instance, we'll go ahead and bind them in the constructor. At the end of the module, there will be code using the ES6 way of just using arrow functions.
Display
Next, we need a way to display our state, so we can add some things to our render() function. Since, we have multiple parts of our state object, and writing this.state.isRunning, this.state.lapTimes, multiple times, we can just declare constants to make it easier to access our state in our render. In the first line of the render, we do this, as seen below. Go ahead and write the following in your render function, then we'll go through it.
So, in our return, we first have our header, and then we have a TimeElapsed component that we have not yet defined. When we see a capitalized component like this, it means it is a react component, one that we have yet to create. We can kind of tell what it's doing though, it has an id of "timer", and it takes in the timeElapsed. We can probably guess that it's going to display the timeElapsed, since that's all it is taking as a prop.
Next, we have a button that has an on click event called "toggle", it then display "Stop" or "Start" based on whether or not isRunning is true. This appears to be how we can stop and start our timer.
The next button has an onClick event of either lap or reset, depending on if isRunning is true, and again the text "Lap" or "Reset". This button will be able to do a couple of different things depending on if isRunning is true.
Last of all, we have a LapTimes component that takes in lapTimes, and presumably, displays them. Again, this is another component we have yet to define, and one that we'll need to.
Note the frequent use of ternary operators, for conditional rendering of things.
Child Components
Next, let's go ahead and create the other react components that we're using to render certain props. Since we don't need to access state in either of state, and they're primarily used to render data, we can make these functional components using arrow functions.
Let's start with the LapTimes component. We want this laptime component to display the lap times, as we press lap and add to them. We're passing lapTimes to this component as a prop, so it needs to take that and format it in a way to display nicely. Let's start by starting off our LapTimes component and using map, map our array of lapTimes into a tr with tds containing the information we want to display. Then to display this, after we've created a variable with the results of our map, we can just simply display that in our return. Go ahead and type out the code for LapTimes outside of the code for your StopWatchApp component.
So now, our react app should know what LapTimes is, and should be able to find it. It still doesn't know what TimeElapsed is, so let's go ahead and create that component.
For TimeElapsed, we are passing it timeElapsed as a prop, and want it to be able to display the time elapsed! First thing we need to do with our timeElapsed, is make sure it's in seconds so that we can convert it, and display it how we want it to be displayed. Then we'll need to figure out minutes from there to display. We can create a function for this called getUnits
.
Next we'll need to zero pad our timer, with 0s as needed to make it look like a normal stopwatch. If we don't do this, we will only start with a single zero for seconds and minutes, which is not how time works. We can create a function called leftPad
to do this. Lastly, we need to display our time elapsed, in our render function, we can do this implementing the other functions we created. Type the code below, underneath LapTimes.
Make sure you read through and understand the above code before moving on. We've created these two functional components to format and display our data how we need it. It's nice to separate concerns into different components so that your code is more easily readable and reusable.
Time Methods
At this point, you should now no longer have any errors, but nothing should happen when you click the Start button, the stopwatch doesn't yet work. So all of our render portions are there, now we need to create the methods to make this thing actually work. If you look back to our constructor, you'll have hints as to what a couple of these methods are. Let's go through them one by one.
Toggle Method
First up we have our toggle method. We need this to basically toggle back and forth whether or not the stop watch is running or not. So when we call this it needs to change the isRunning
part of our state.
So the first part of toggle method needs to set isRunning to the opposite of what it is, like so:
Then we need it to either start the timer if isRunning is now true, or to stop the timer (clearInterval), if isRunning is now false. Type the following code below into your StopWatchApp
below your constructor.
startTimer()
Now that we have toggle, we actually need a startTimer()
method for toggle to call to start the timer. This is going to look similar to the timers we have used before, but this time instead of updating every 1000ms, we want it to update more frequently since we're intersted in a smaller increment of time for a stopwatch. Type the following code below your toggle()
method in your StopWatchApp.
Update Method
We now need an update method, to use in our startTimer()
. Timer will use this update method every 10ms, to update the displayed time. update()
is going to use the initial startTime, and then set the new startTime, for future use. Every time it will set a new timeElapsed, based on the old time elapse, and the difference between the startTime and the current time. This component is essential to constantly update the timeElapsed.
lap method
Next we need our lap()
method. This method will be called when the lap button is pressed, and will add on a new time to the lapTimes every time it is called. If you remember our initial state, lapTime start as an empty array, so we'll need to add a new lap time to the array every time. Add the following code below your update()
method.
reset method
Lastly, we need a reset()
method, to completely reset the stopwatch when the reset button is clicked. This method will clearInterval on our timer, like in our other timers, and set the state back to our initial empty state. Add the following code below your lap()
method.
Run the app
Now you should have a working stop watch!!!
Make sure you carefully look through the code, and understand how this app is working. This app is definitely more complex than the previous two, but uses all of the same concepts. If you don't understand how any part of this app works, ask a neighbor or an instructor. The following code is the original with the functions bound in the constructor.
ES6 Version
Now, as promised, this is the code refactored to use ES6 arrow functions, which do not bind this, and therefore do not need to be bound in the constructor.
Last updated