TIL/2021–02–03&04

Renzo Regio
7 min readFeb 4, 2021

Day 55 and 56: Treehouse Full Stack JavaScript Techdegree

Designing the stopwatch

Now we will build a stopwatch into our scoreboard app — it will be a stateful component that counts seconds. Allows users to stop, start and reset the time

We’ll create a component and use state and functions to let users stop, start and reset the time

Running state, elapsed time state and a previous time state to let us calculate how much time has passed — component state — which means our stopwatch would be a stateful component

The state of the stopwatch will need to be updating constantly and will need to keep track of multiple pieces of state

import React, { Component } from “react”;

class Stopwatch extends Component {

render() {

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>0</span>

<button>Start</button>

<button>Reset</button>

</div>

);

}

}

export default Stopwatch;

const Header = (props) => {

return (

<header>

<Stats players={props.players} />

<h1>{props.title}</h1>

<Stopwatch />

</header>

);

};

— — -

Stopwatch State

Two main states visible to the user — running state and stop state

Buttons on the interface should change based on the running state (Start changes to stop when it’s on the running state and vice versa

We can use the ternary operator

let button = condition ? <if true then execute this code> : <if false execute this code>

class Stopwatch extends Component {

state = {

isRunning: true,

};

render() {

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>0</span>

<button>{this.state.isRunning ? “Stop” : “Start”}</button>

<button>Reset</button>

</div>

);

}

}

— — -

Now let’s add an onClick event to the start/stop button that will call the function named handle stopwatch that will start or stop the timer

We can toggle the isRunning state by using the logical not operator of its current true or false value

handleStopwatch = () => {

this.setState({

isRunning: !this.state.isRunning,

});

};

render() {

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>0</span>

<button onClick={this.handleStopwatch}>

{this.state.isRunning ? “Stop” : “Start”}

</button>

<button>Reset</button>

</div>

);

}

}

— — -

Update the Stopwatch State with componentDidMount()

We will create a tick function. All the code that involves making the timer tick second after second will be written in the tick function. The tick function will need to be called continually as long as the stopwatch component is mounted on the DOM or is visible on the screen.

How do we call tick over and over again? We can’t do it in render() because it is for rendering only.

We can use one of React’s built-in life cycle methods

Component Lifecycle:

  • Every component instance follows a cycle: it’s mounted onto the DOM, it is updated with changes in the data, and it is unmounted from the DOM.

React Lifecycle

  • Built-in methods that get called at each point in the life cycle.
  • Hooks that run code at key times in a component’s life cycle
  • Gives you the ability to control what happens when a component mounts, updates and unmounts

The lifecycle method that we’ll use the most is componentDidMount()

componentDidMount()

  • Gets called by react as soon as a component is inserted or mounted into the DOM.

componentDidMount() {

console.log(“the stopwatch mounted”);

}

And once I reload the page, the console shows the message. Because it is true, the stopwatch component is mounted on the page since we inserted it in the header.

componentDidMount() is a lifecycle method

  • Also referred to as lifecycle hooks because they let you hook into or provide hooks into certain parts of a components life cycle
  • Because componentDidMount() is immediately called after a component mounts, it is a convenient hook for setting up timers, fetching data or anything else you might need to do when your component is mounted on a page

setInterval method returns an interval ID which uniquely identifies the interval

tick = () => {

console.log(“It is ticking!”);

};

componentDidMount() {

this.intervalID = setInterval(() => this.tick(), 100);

}

And as you can see, it works!

Date.now() returns the exact number of milliseconds elapsed since January 1st of 1970 or Epoch Time and it is how dates and time are stored on JavaScript

If we call Date.now() in the console it returns a number in milliseconds. Each time we call it, it increases. This is a convenient way to track time intervals in between clicks

Pay attention to the last three digits

In our tick function we have to update the elapsed time state only if isRunning is true

So basically calling Date.now() shows the current time in milliseconds

ElapsedTime has to increase by the amount of milliseconds since the last tick — we can determine the difference of the elapsed time between now’s timestamp

import React, { Component } from “react”;

class Stopwatch extends Component {

state = {

isRunning: false,

elapsedTime: 0,

previousTime: 0,

};

handleStopwatch = () => {

this.setState({

isRunning: !this.state.isRunning,

});

if (!this.state.isRunning) {

this.setState({ previousTime: Date.now() });

}

};

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.state.isRunning) {

this.setState({ previousTime: Date.now() });

}

};

componentDidMount() {

this.intervalID = setInterval(() => this.tick(), 100);

}

render() {

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>0</span>

<button onClick={this.handleStopwatch}>

{this.state.isRunning ? “Stop” : “Start”}

</button>

<button>Reset</button>

</div>

);

}

}

export default Stopwatch;

A stopwatch usually consists of a counter that is always counting up. We can pause it but anytime that the counter is running it should be counting up.

We are going to put that data and state in a property called elapsedTime. In order to increase the time each time tick() is called, we have to calculate the amount of time since the last tick and add it to elapsedTime

In order to get the time interval between ticks, we need to keep track of the the time at which the previous tick happened — previousTime. We will only update the previous time once we start the timer

— -

It starts when the isRunning state is true

It stops when the isRunning state is false

— — -

Resetting the stopwatch

In order to display the stopwatch time in seconds we have to do one more calculation that converts the elapsed time in milliseconds to seconds

If we display the elapsed time with

<span className=”stopwatch-time”>{this.state.elapsedTime}</span>

We will get the milliseconds and not the seconds

To get the seconds we have to divide the elapsed time by 1000 and to get a rounded value we can use math.floor

<span className=”stopwatch-time”>

{Math.floor(this.state.elapsedTime / 1000)}

</span>

In the render method, any intermediary variables should be written outside the return statement

render() {

const timer = Math.floor(this.state.elapsedTime / 1000);

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>{timer}</span>

<button onClick={this.handleStopwatch}>

{this.state.isRunning ? “Stop” : “Start”}

</button>

<button>Reset</button>

</div>

);

}

}

Reset button

resetStopwatch = () => {

this.setState({ elapsedTime: 0 });

};

componentDidMount() {

this.intervalID = setInterval(() => this.tick(), 100);

}

render() {

const timer = Math.floor(this.state.elapsedTime / 1000);

return (

<div className=”stopwatch”>

<h2>Stopwatch</h2>

<span className=”stopwatch-time”>{timer}</span>

<button onClick={this.handleStopwatch}>

{this.state.isRunning ? “Stop” : “Start”}

</button>

<button onClick={this.resetStopwatch}>Reset</button>

</div>

);

}

}

— —

Prevent memory leaks with componentWillUnmount()

Since components do not always stay in the DOM, React also provides the componentWillUnmount lifecycle method to help you handle unmounting of components. This can help prevent memory leaks in your application.

If we unmount the stopwatch component, setInterval would still keep calling the tick function repeatedly. Because setInterval doesn’t get cleared, it will cause a memory leak which will negatively impact the performance of our app.

If we unmount the stopwatch it will still keep on running and react will show an error.

The stopwatch component was unmounted but the tick function is still in memory (we unmounted it after reloading the page by clicking SCOREBOARD) attempting to update a component or function that no longer exists

The interval we set in componentDidMount() was never cleared and what is happening above is the memory leak.

We don’t have a problem with this because our stopwatch would always be visible. But what if we allowed users to toggle and remove the stopwatch.

To handle this:

Since components do not always stay in the DOM, React also provides the lifecycle method componentWillUnmount() to help us with unmounting of components

componentWillUnmount is invoked just before a component instance is unmounted and destroyed.

So we can use it to clear anything that needs to be cleared when a component is removed from the DOM.

We can clear the setInverval with the clearInterval method that will receive the ID variable of the setInterval method we created earlier

clearInterval() method clears or cancels any timed repeating actions created by calling setInterval.

componentWillUnmount() {

clearInterval(this.intervalID);

}

Every time we use the componentDidMount() life cycle method, we should also think what will happen if the component unmounts.

Using componentWillUnmount, you should always perform cleanups that should be done. Like clearing timers, clearing active network requests or removing or tearing down any DOM elements that were created during componentDidMount

--

--