CHAPTER-2

Components

A time-logging app

In the last chapter, we described how React organizes apps into components and how data flows between parent and child components. And we discussed core concepts such as how we manage state and pass data between components using props.

In this chapter, we construct a more intricate application. We investigate a pattern that you can use to build React apps from scratch and then put those steps to work to build an interface for managing timers.

In this time-tracking app, a user can add, delete, and modify various timers. Each timer corresponds to a different task that the user would like to keep time for:

This app will have significantly more interactive capabilities than the one built in the last chapter. This will present us with some interesting challenges that will deepen our familiarity with React’s core concepts.

Getting started

As with all chapters, before beginning make sure you’ve downloaded the book’s sample code and have it at the ready.

Previewing the app

Let’s begin by playing around with a completed implementation of the app.
In your terminal, cd into the time_tracking_app directory:

$ cd time_tracking_app

Use npm to install all the dependencies:

$ npm install

Then boot the server:

$ npm start

Now you can view the app in your browser. Open your browser and enter the URL http://localhost:3000.

Play around with it for a few minutes to get a feel for all the functionality. Refresh and note that your changes have been persisted.

Note that this app uses a different web server than the one used in the voting app. The app won’t automatically launch in your browser or
automatically refresh when you make changes.

Prepare the app

In your terminal, run ls to see the project’s layout:

$ ls
README.md
data.json
nightwatch.json
node_modules/
package.json
public/
semantic.json
server.js
tests/

There are a few organizational changes from the last project.

First, notice that there is now a server.js in this project. In the last chapter, we used a pre-built Node package (called live-server) to serve our assets.

This time we have a custom-built server which serves our assets and also adds a persistence layer. We will cover the server in detail in the next chapter.

This image has an empty alt attribute; its file name is image-437.pngWhen you visit a website, assets are the files that your browser downloads and uses to display the page. index.html is delivered to the browser and inside its head tags it specifies which additional files from the server the browser needs to download.

In the last project, our assets were index.html as well as our stylesheets and images.

In this project, everything under public/ is an asset.

In the voting app, we loaded all of our app’s initial data from a JavaScript variable, loaded in the file seed.js.

This time, we’re going to eventually store it in the text file data.json. This brings the behavior a bit closer to a database. By using a JSON file, we can make edits to our data that will be persisted even if the app is closed.

JSON stands for JavaScript Object Notation. JSON enables us to serialize a JavaScript object and read/write it from a text file.

If you’re not familiar with JSON, take a look at data.json. Pretty recognizable, right? JavaScript has a built-in mechanism to parse the contents of this file and initialize a JavaScript object with its data.

Peek inside public:

$ cd public
$ ls

The structure here is the same as the last project:

favicon.ico
index.html
js/
semantic/
style.css
vendor/

Again, index.html is the centerpiece of the app. It’s where we include all of our JavaScript and CSS files and where we specify the DOM node where we’ll ultimately mount our React app.

We’re using SemanticUI again here for styling. All of SemanticUI’s assets are underneath semantic/.

All our JavaScript files are inside of js/:

$ ls js/
app-1.js
app-2.js
app-3.js
app-4.js
app-5.js
app-6.js
app-7.js
app-8.js
app-9.js
app-complete.js
app.js
client.js
helpers.js

We’ll be building the app inside app.js. The completed version of the app whichwe reach in the next chapter is inside app-complete.js. Each step we take along the way is included here: app-1.js, app-2.js, and so forth. Like the last chapter, code examples in the book are titled with the file in which you can find that example.

Furthermore, we’ll be using a couple additional JavaScript files for this project. As we’ll see, client.js contains functions that we’ll use to interface with our server in the next chapter. helpers.js contains some helper functions that our components will use.

As before, our first step is to ensure app-complete.js is no longer loaded in index.html. We’ll instead load the empty file app.js.

Open up index.html:

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <title>Project Two: Timers</title>
    <link rel="stylesheet" href="./semantic-dist/semantic.css" />
    <link rel="stylesheet" href="style.css" />
    <script src="vendor/babel-standalone.js"></script>
    <script src="vendor/react.js"></script>
    <script src="vendor/react-dom.js"></script>
    <script src="vendor/uuid.js"></script>
    <script src="vendor/fetch.js"></script>
  </head>

  <body>
    <div id="main" class="main ui">
      <h1 class="ui dividing centered header">Timers</h1>
      <div id="content"></div>
    </div>
    <script type="text/babel" src="./js/client.js"></script>
    <script type="text/babel" src="./js/helpers.js"></script>
    <script
      type="text/babel"
      data-plugins="transform-class-properties"
      src="./js/app.js"
    ></script>
    <!-- Delete the script tag below to get started. -->

    <script
      type="text/babel"
      data-plugins="transform-class-properties"
      src="./js/app-complete.js"
    ></script>
  </body>

</html>

Overall, this file is very similar to the one we used in our voting app. We load in our dependencies within the head tags (the assets). Inside of body we have a few elements. This div is where we will ultimately mount our React app:

<div id="content"></div>

And this script tag is where we instruct the browser to load app.js into the page:

<script
  type="text/babel"
  data-plugins="transform-class-properties"
  src="./js/app.js"
></script>

We’re using the Babel plugin transform-class-properties again in this chapter. We discussed this plugin at the end of the previous chapter.

Do as the comment says and delete the script tag that loads app-complete.js:

Save index.html. If you reload the page now, you’ll see the app has disappeared.

Breaking the app into components

As we did with our last project, we begin by breaking our app down into its components. Again, visual components often map tightly to their respective React components. Let’s examine the interface of our app:

In the last project, we had ProductList and Product components. The first contained instances of the second. Here, we spot the same pattern, this time with TimerList and Timer components:

However, there’s one subtle difference: This list of timers has a little “+” icon at the bottom. As we saw, we’re able to add new timers to the list using this button. So, in reality, the TimerList component isn’t just a list of timers. It also contains a widget to create new timers.

Think about components as you would functions or objects. The single responsibility principle³² applies. A component should, ideally, only be responsible for one piece of functionality. So, the proper response here is for us to shrink TimerList back into its responsibility of just listing timers and to nest it under a parent component. We’ll call the parent TimersDashboard. TimersDashboard will have TimerList and the “+”/create form widget as children:

Not only does this separation of responsibilities keep components simple, but it often also improves their re-usability. In the future, we can now drop the TimerList component anywhere in the app where we just want to display a list of timers. This component no longer carries the responsibility of also creating timers, which might be a behavior we want to have for just this dashboard view.

How you name your components is indeed up to you, but having some consistent rules around language as we do here will greatly improve code clarity.

In this case, developers can quickly reason that any component they come across that ends in the word List simply renders a list of children and no more.

The “+”/create form widget is interesting because it has two distinct representations. When the “+” button is clicked, the widget transmutes into a form. When the form is closed, the widget transmutes back into a “+” button.

There are two approaches we could take. The first one is to have the parent component, TimersDashboard, decide whether or not to render a “+” component or a form component based on some piece of stateful data. It could swap between the two children. However, this adds more responsibility to TimersDashboard. The alternative is to have a new child component own the single responsibility of determining whether or not to display a “+” button or a create timer form. We’ll call it ToggleableTimerForm. As a child, it can either render the component TimerForm or the HTML markup for the “+” button.

At this point, we’ve carved out four components:

Now that we have a sharp eye for identifying overburdened components, another candidate should catch our eye:

The timer itself has a fair bit of functionality. It can transform into an edit form, delete itself, and start and stop itself. Do we need to break this up? And if so, how?

Displaying a timer and editing a timer are indeed two distinct UI elements. They should be two distinct React components. Like ToggleableTimerForm, we need some container component that renders either the timer’s face or its edit form depending on if the timer is being edited.

We’ll call this EditableTimer. The child of EditableTimer will then be either a Timer component or the edit form component. The form for creating and editing timers is very similar, so let’s assume that we can use the component TimerForm in both contexts:

As for the other functionality of the timer, like the start and stop buttons, it’s a bit tough to determine at this point whether or not they should be their own components. We can trust that the answers will be more apparent after we’ve written some code.

Working back up the component tree, we can see that the name TimerList would be a misnomer. It really is a EditableTimerList. Everything else looks good.

So, we have our final component hierarchy, with some ambiguity around the final state of the timer component:

  • TimersDashboard: Parent container
    – EditableTimerList: Displays a list of timer containers
  • *EditableTimer: Displays either a timer or a timer’s edit form
    · Timer: Displays a given timer
    · TimerForm: Displays a given timer’s edit form
    – ToggleableTimerForm: Displays a form to create a new timer
  • TimerForm (not displayed): Displays a new timer’s create form

Represented as a hierarchical tree:

This image has an empty alt attribute; its file name is image-439.pngIn our previous app, ProductList handled not only rendering components, but also the responsibility of handling up-votes and talking to the store. While this worked for that app, you can imagine that as a codebase expands, there may come a day where we’d want to free ProductList of this responsibility.

For example, imagine if we added a “sort by votes” feature to ProductList. What if we wanted some pages to be sortable (category pages) but other pages to be static (top 10)? We’d want to “hoist” sort responsibility up to a parent component and make ProductList the traightforward list renderer that it should be.

This new parent component could then include the sorting-widget component and then pass down the ordered products to the ProductList component.

The steps for building React apps from scratch

Now that we have a good understanding of the composition of our components, we’re ready to build a static version of our app. Ultimately, our top-level component will communicate with a server. The server will be the initial source of state, and React will render itself according to the data the server provides. Our app will also send updates to the server, like when a timer is started:

However, it will simplify things for us if we start off with static components, as we did in the last chapter. Our React components will do little more than render HTML. Clicking on buttons won’t yield any behavior as we will not have wired up any interactivity. This will enable us to lay the framework for the app, getting a clear idea of how the component tree is organized.

Next, we can determine what the state should be for the app and in which component it should live. We’ll start off by just hard-coding the state into the components instead of loading it from the server.

At that point, we’ll have the data flow from parent to child in place. Then we can add inverse data flow, propagating events from child to parent.

Finally, we’ll modify the top-level component to have it communicate with the server.

In fact, this follows from a handy framework for developing a React app from scratch:

  1. Break the app into components
  2. Build a static version of the app
  3. Determine what should be stateful
  4. Determine in which component each piece of state should live
  5. Hard-code initial states
  6. Add inverse data flow
  7. Add server communication

We followed this pattern in the last project:

  1. Break the app into components
    We looked at the desired UI and determined we wanted ProductList and Product components.
  1. Build a static version of the app
    Our components started off without using state. Instead, we had ProductList pass down static props to Product.
  1. Determine what should be stateful
    In order for our application to become interactive, we had to be able to modify the vote property on each product. Each product had to be mutable and therefore stateful.
  1. Determine in which component each piece of state should live
    ProductList managed the voting state using React component class methods.
  1. Hard-code initial state
    When we re-wrote ProductList to use this.state, we seeded it from Seed.products.
  1. Add inverse data flow
    We defined the handleUpVote function in ProductList and passed it down in props
    so that each Product could inform ProductList of up-vote events.

7. Add server communication

We did not add a server component to our last app, but we will be doing so in this one.

If steps in this process aren’t completely clear right now, don’t worry. The purpose of this chapter is to familiarize yourself with this procedure.

We’ve already covered step (1) and have a good understanding of all of our components, save for some uncertainty down at the Timer component. Step (2) is to build a static version of the app. As in the last project, this amounts to defining React components, their hierarchy, and their HTML representation. We completely avoid state for now.

Step 2: Build a static version of the app

TimersDashboard

Let’s start off with the TimersDashboard component. Again, all of our React code for this chapter will be inside of the file public/app.js.

We’ll begin by defining a familiar function, render():

class TimersDashboard extends React.Component {
  render() {
    return (
      <div className='ui three column centered grid'>
        <div className='column'>
          <EditableTimerList />
          <ToggleableTimerForm
            isOpen={true}
          />
        </div>
       </div>
     );
   }
 }

This component renders its two child components nested under div tags. TimersDashboard passes down one prop to ToggleableTimerForm: isOpen. This is used by the child component to determine whether to render a “+” or TimerForm. When ToggleableTimerForm is “open” the form is being displayed.

As in the last chapter, don’t worry about the className attribute on the div tags. This will ultimately define the class on HTML div elements and is purely for styling purposes.

In this example, classes like ui three column centered grid all come from the CSS framework Semantic UI³³. The framework is included in the head of index.html.

We will define EditableTimerList next. We’ll have it render two EditableTimer components. One will end up rendering a timer’s face. The other will render a timer’s edit form:

class EditableTimerList extends React.Component {
  render() {
    return (
      <div id='timers'>
        <EditableTimer
          title='Learn React'
          project='Web Domination'
          elapsed='8986300'
          runningSince={null}
          editFormOpen={false}
        />
        <EditableTimer
          title='Learn extreme ironing'
          project='World Domination'
          elapsed='3890985'
          runningSince={null}
          editFormOpen={true}
        />
      </div>
    );
  }
}

We’re passing five props to each child component. The key difference between the two EditableTimer components is the value being set for editFormOpen. We’ll use this boolean to instruct EditableTimer which sub-component to render.

The purpose of the prop runningSince will be covered later on in the app’s development.

EditableTimer

EditableTimer returns either a TimerForm or a Timer based on the prop editFormOpen:

class EditableTimer extends React.Component {
  render() {
    if (this.props.editFormOpen) {
      return (
        <TimerForm
          title={this.props.title}
          project={this.props.project}
        />
      );
    } else {
      return (
        <Timer
          title={this.props.title}
          project={this.props.project}
          elapsed={this.props.elapsed}
          runningSince={this.props.runningSince}
        />
      );
    }
  }
}

Note that title and project are passed down as props to TimerForm. This will enable the component to fill in these fields with the timer’s current values.

TimerForm

We’ll build an HTML form that will have two input fields. The first input field is for the title and the second is for the project. It also has a pair of buttons at the bottom:

class TimerForm extends React.Component {
  render() {
    const submitText = this.props.title ? 'Update' : 'Create';
    return (
      <div className='ui centered card'>
        <div className='content'>
          <div className='ui form'>
            <div className='field'>
              <label>Title</label>
              <input type='text' defaultValue={this.props.title} />
            </div>
            <div className='field'>
              <label>Project</label>
              <input type='text' defaultValue={this.props.project} />
            </div>
            <div className='ui two bottom attached buttons'>
              <button className='ui basic blue button'>
                {submitText}
              </button>
              <button className='ui basic red button'>
              Cancel
            </button>
           </div>
          </div>
         </div>
        </div>
      );
   }
}

Look at the input tags. We’re specifying that they have type of text and then we are using the React property defaultValue. When the form is used for editing as it is here, this sets the fields to the current values of the timer as desired.

This image has an empty alt attribute; its file name is image-441.pngLater, we’ll use TimerForm again within ToggleableTimerForm for creating timers. ToggleableTimerForm will not pass TimerForm any props. this.props.title and this.props.project will therefore return undefined and the fields will be left empty.

At the beginning of render(), before the return statement, we define a variable submitText. This variable uses the presence of this.props.title to determine what text the submit button at the bottom of the form should display. If title is present, we know we’re editing an existing timer, so it displays “Update.” Otherwise, it displays “Create.”

With all of this logic in place, TimerForm is prepared to render a form for creating a new timer or editing an existing one.

We used an expression with the ternary operator to set the value of submitText. The syntax is:

condition ? expression1 : expression2

If the condition is true, the operator returns the value of expression1. Otherwise, it returns the value of expression2. In our example, the variable submitText is set to the returned expression.

ToggleableTimerForm

Let’s turn our attention next to ToggleableTimerForm. Recall that this is a wrapper component around TimerForm. It will display either a “+” or a TimerForm. Right now, it accepts a single prop, isOpen, from its parent that instructs its behavior:

class ToggleableTimerForm extends React.Component {
  render() {
    if (this.props.isOpen) {
      return (
        <TimerForm />
      );
    } else {
      return (
        <div className='ui basic content center aligned segment'>
          <button className='ui basic button icon'>
            <i className='plus icon' />
          </button>
         </div>
       );
     }
   }
 }

As noted earlier, TimerForm does not receive any props from ToggleableTimerForm. As such, its title and project fields will be rendered empty.

The return statement under the else block is the markup to render a “+” button. You could make a case that this should be its own React component (say PlusButton) but at present we’ll keep the code inside ToggleableTimerForm.

Timer

Time for the Timer component. Again, don’t worry about all the div and span elements and className attributes. We’ve provided these for styling purposes:

class Timer extends React.Component {
  render() {
    const elapsedString = helpers.renderElapsedString(this.props.elapse\
d);
    return (
      <div className='ui centered card'>
        <div className='content'>
          <div className='header'>
            {this.props.title}
          </div>
          <div className='meta'>
            {this.props.project}
          </div>
          <div className='center aligned description'>
            <h2>
              {elapsedString}
            </h2>
           </div>
           <div className='extra content'>
             <span className='right floated edit icon'>
               <i className='edit icon' />
             </span>
             <span className='right floated trash icon'>
               <i className='trash icon' />
             </span>
            </div>
           </div>
           <div className='ui bottom attached blue basic button'>
             Start
           </div>
          </div>
        );
     }
  }

elapsed in this app is in milliseconds. This is the representation of the data that React will keep. This is a good representation for machines, but we want to show our carbon-based users a more readable format.

We use a function defined in helpers.js, renderElapsedString(). You can pop open that file if you’re curious about how it’s implemented. The string it renders is in the format ‘HH:MM:SS’.

Note that we could store elapsed in seconds as opposed to milliseconds, but JavaScript’s time functionality is all in milliseconds. We keep elapsed consistent with this for simplicity. As a bonus, our timers are also slightly more accurate, even though they round to seconds when displayed to the user.

Render the app

With all of the components defined, the last step before we can view our static app is to ensure we call ReactDOM#render(). Put this at the bottom of the file:

ReactDOM.render(
  <TimersDashboard />,
  document.getElementById('content')
);

Again, we specify with ReactDOM#render() which React component we want to render and where in our HTML document (index.html) to render it.

In this case, we’re rendering TimersDashboard at the div with the id of content.

Try it out

Save app.js and boot the server (npm start). Find it at localhost:3000:

Tweak some of the props and refresh to see the results. For example:

  • Flip the prop passed down to ToggleableTimerForm from true to false and see the “+” button render.
  • Flip parameters on editFormOpen and witness EditableTimer flip the child it renders accordingly.

Let’s review all of the components represented on the page:

Inside TimersDashboard are two child components: EditableTimerList and ToggleableTimerForm.

EditableTimerList contains two EditableTimer components. The first of these has a Timer component as a child and the second a TimerForm. These bottom-level components — also known as leaf components — hold the majority of the page’s HTML. This is generally the case. The components above leaf components are primarily concerned with orchestration.

ToggleableTimerForm renders a TimerForm. Notice how the two forms on the page have different language for their buttons, as the first is updating and the second is creating.

Step 3: Determine what should be stateful

In order to bestow our app with interactivity, we must evolve it from its static existence to a mutable one. The first step is determining what, exactly, should be mutable. Let’s start by collecting all of the data that’s employed by each component in our static app. In our static app, data will be wherever we are defining or using props. We will then determine which of that data should be stateful.

TimersDashboard

In our static app, this declares two child components. It sets one prop, which is the isOpen boolean that is passed down to ToggleableTimerForm.

EditableTimerList

This declares two child components, each which have props corresponding to a given timer’s properties.

EditableTimer

This uses the prop editFormOpen.

Timer

This uses all the props for a timer.

TimerForm

This has two interactive input fields, one for title and one for project. When editing an existing timer, these fields are initialized with the timer’s current values.

State criteria

We can apply criteria to determine if data should be stateful:

These questions are from the excellent article by Facebook called “Thinking In React”. You can read the original article here³⁴.

1.Is it passed in from a parent via props? If so, it probably isn’t state.

A lot of the data used in our child components are already listed in their parents. This criterion helps us de-duplicate.
For example, “timer properties” is listed multiple times. When we see the properties declared in EditableTimerList, we can consider it state. But when we see it elsewhere, it’s not.

2.Does it change over time? If not, it probably isn’t state.

This is a key criterion of stateful data: it changes.

3.Can you compute it based on any other state or props in your component? If so, it’s not state.

For simplicity, we want to strive to represent state with as few data points as possible.

Applying the criteria

TimersDashboard
  • isOpen boolean for ToggleableTimerForm

Stateful. The data is defined here. It changes over time. And it cannot be computed from other state or props.

EditableTimerList
  • Timer properties

Stateful. The data is defined in this component, changes over time, and cannot be computed from other state or props.

EditableTimer
  • editFormOpen for a given timer

Stateful. The data is defined in this component, changes over time, and cannot be computed from other state or props.

Timer
  • Timer properties

In this context, not stateful. Properties are passed down from the parent.

TimerForm

We might be tempted to conclude that TimerForm doesn’t manage any stateful data, as title and project are props passed down from the parent. However, as we’ll see, forms are special state managers in their own right.

So, outside of TimerForm, we’ve identified our stateful data:

  • The list of timers and properties of each timer
  • Whether or not the edit form of a timer is open
  • Whether or not the create form is open

Step 4: Determine in which component each piece of state should live

While the data we’ve determined to be stateful might live in certain components in our static app, this does not indicate the best position for it in our stateful app. Our next task is to determine the optimal place for each of our three discrete pieces of state to live.

This can be challenging at times but, again, we can apply the following steps from Facebook’s guide “Thinking in React³⁵” to help us with the process:

For each piece of state:

  • Identify every component that renders something based on that state.
  • Find a common owner component (a single component above all the components that need the state in the hierarchy).
  • Either the common owner or another component higher up in the hierarchy should own the state.
  • If you can’t find a component where it makes sense to own the state, create a new component simply for holding the state and add it
    somewhere in the hierarchy above the common owner component.

Let’s apply this method to our application:

The list of timers and properties of each timer

At first glance, we may be tempted to conclude that TimersDashboard does not appear to use this state. Instead, the first component that uses it is EditableTimerList. This matches the location of the declaration of this data in our static app. Because ToggleableTimerForm doesn’t appear to use the state either, we might deduce that EditableTimerList must then be the common owner.

While this may be the case for displaying timers, modifying them, and deleting them, what about creates? ToggleableTimerForm does not need the state to render, but it can affect state. It needs to be able to insert a new timer. It will propagate the data for the new timer up to TimersDashboard.

Therefore, TimersDashboard is truly the common owner. It will render EditableTimerList by passing down the timer state. It can handle modifications fromEditableTimerList and creates from ToggleableTimerForm, mutating the state. The new state will flow downward through EditableTimerList.

Whether or not the edit form of a timer is open

In our static app, EditableTimerList specifies whether or not an EditableTimer should be rendered with its edit form open. Technically, though, this state could just live in each individual EditableTimer. No parent component in the hierarchy depends on this data.

Storing the state in EditableTimer will be fine for our current needs. But there are a few requirements that might require us to “hoist” this state up higher in the component hierarchy in the future.

For instance, what if we wanted to impose a restriction such that only one edit form could be open at a time? Then it would make sense for EditableTimerList to own the state, as it would need to inspect it to determine whether to allow a new “edit form open” event to succeed. If we wanted to allow only one form open at all, including the create form, then we’d hoist the state up to TimersDashboard.

Visibility of the create form

TimersDashboard doesn’t appear to care about whether ToggleableTimerForm is open or closed. It feels safe to reason that the state can just live inside ToggleableTimerForm itself.

So, in summary, we’ll have three pieces of state each in three different components:

  • Timer data will be owned and managed by TimersDashboard.
  • Each EditableTimer will manage the state of its timer edit form.
  • The ToggleableTimerForm will manage the state of its form visibility.

Step 5: Hard-code initial states

We’re now well prepared to make our app stateful. At this stage, we won’t yet communicate with the server. Instead, we’ll define our initial states within the components themselves. This means hard-coding a list of timers in the top-level component, TimersDashboard. For our two other pieces of state, we’ll have the components’ forms closed by default.

After we’ve added initial state to a parent component, we’ll make sure our props are properly established in its children.

Adding state to TimersDashboard

Start by modifying TimersDashboard to hold the timer data directly inside the component:

class TimersDashboard extends React.Component {
  state = {
    timers: [
      {
        title: 'Practice squat',
        project: 'Gym Chores',
        id: uuid.v4(),
        elapsed: 5456099,
        runningSince: Date.now(),
      },
      {
        title: 'Bake squash',
        project: 'Kitchen Chores',
        id: uuid.v4(),
        elapsed: 1273998,
        runningSince: null,
      },
   ],
};
render() {
  return (
    <div className='ui three column centered grid'>
      <div className='column'>
        <EditableTimerList
          timers={this.state.timers}
        />
        <ToggleableTimerForm />
      </div>
     </div>
   );
 }
}

We’re leaning on the Babel plugin transform-class-properties to give us the property initializers syntax. We set the initial state to an object with the key timers. timers points to an array with two hard-coded timer objects.

We discuss property initializers in the previous chapter.

Below, in render, we pass down state.timers to EditableTimerList.
For the id property, we’re using a library called uuid. We load this library in index.html. We use uuid.v4() to randomly generate a Universally Unique IDentifier³⁶ for each item.

A UUID is a string that looks like this:

2030efbd-a32f-4fcc-8637-7c410896b3e3

Receiving props in EditableTimerList

EditableTimerList receives the list of timers as a prop, timers. Modify that component to use those props:

class EditableTimerList extends React.Component {
  render() {
    const timers = this.props.timers.map((timer) => (
      <EditableTimer
        key={timer.id}
        id={timer.id}
        title={timer.title}
        project={timer.project}
        elapsed={timer.elapsed}
        runningSince={timer.runningSince}
      />
    ));
    return (
      <div id='timers'>
        {timers}
      </div>
    );
  }
}

Hopefully this looks familiar. We’re using map on the timers array to build a list of EditableTimer components. This is exactly how we built our list of Product components inside ProductList in the last chapter.

We pass the id down to EditableTimer as well. This is a bit of eager preparation. Remember how Product communicated up to ProductList by calling a function and passing in its id? It’s safe to assume we’ll be doing this again.

Props vs. state

With your renewed understanding of React’s state paradigm, let’s reflect on props again.

Remember, props are state’s immutable accomplice. What existed as mutable state in TimersDashboard is passed down as immutable props to EditableTimerList.

We talked at length about what qualifies as state and where state should live. Mercifully, we do not need to have an equally lengthy discussion about props. Once you understand state, you can see how props act as its one-way data pipeline. State is managed in some select parent components and then that data flows down through children as props.

If state is updated, the component managing that state re-renders by calling render(). This, in turn, causes any of its children to re-render as well. And the children of those children. And on and on down the chain.

Let’s continue our own march down the chain.

Adding state to EditableTimer

In the static version of our app, EditableTimer relied on editFormOpen as a prop to be passed down from the parent. We decided that this state could actually live here in the component itself.

We’ll set the initial value of editFormOpen to false, which means that the form starts off as closed. We’ll also pass the id property down the chain:

class EditableTimer extends React.Component {
  state = {
    editFormOpen: false,
  };

  render() {
    if (this.state.editFormOpen) {
      return (
        <TimerForm
          id={this.props.id}
          title={this.props.title}
          project={this.props.project}
        />
      );
    } else {
      return (
        <Timer
          id={this.props.id}
          title={this.props.title}
          project={this.props.project}
          elapsed={this.props.elapsed}
          runningSince={this.props.runningSince}
        />
      );
    }
  }
}

Timer remains stateless

If you look at Timer, you’ll see that it does not need to be modified. It has been using exclusively props and is so far unaffected by our refactor.

Adding state to ToggleableTimerForm

We know that we’ll need to tweak ToggleableTimerForm as we’ve assigned it some stateful responsibility. We want to have the component manage the state isOpen. Because this state is isolated to this component, let’s also add our app’s first bit of interactivity while we’re here.

Let’s start by initializing the state. We want the component to initialize to a closed state:

class ToggleableTimerForm extends React.Component {
  state = {
    isOpen: false,
  };

Next, let’s define a function that will toggle the state of the form to open:

handleFormOpen = () => {
  this.setState({ isOpen: true });
};

render() {

As we explored at the end of the last chapter, we need to write this function as an arrow function in order to ensure this inside the function is bound to the component. React will automatically bind class methods corresponding to the component API (like render and componentDidMount) to the component for us.

As a refresher, without the property initializer feature we’d write our custom component method like this:

handleFormOpen() {
  this.setState({ isOpen: true });
}

Our next step would be to bind this method to the component inside the constructor, like this:

constructor(props) {
  super(props);
  
  this.handleFormOpen = this.handleFormOpen.bind(this);
}

This is a perfectly valid approach and does not use any features beyond ES7. However, we’ll be using property initializers for this project.

While we’re here, we can also add a little bit of interactivity:

render() {
  if (this.state.isOpen) {
    return (
      <TimerForm />
    );
  } else {
    return (
      <div className='ui basic content center aligned segment'>
        <button
          className='ui basic button icon'
          onClick={this.handleFormOpen}
        >
          <i className='plus icon' />
        </button>
       </div>
    );
  }
}

Like the up-vote button in the last app, we use the onClick property on button to invoke the function handleFormOpen(). handleFormOpen() modifies the state, setting isOpen to true. This causes the component to re-render. When render() is called this
second time around, this.state.isOpen is true and ToggleableTimerForm renders TimerForm. Neat.

Adding state to TimerForm

We mentioned earlier that TimerForm would manage state as it includes a form. In React, forms are stateful.

Recall that TimerForm includes two input fields:

These input fields are modifiable by the user. In React, all modifications that are made to a component should be handled by React and kept in state. This includes changes like the modification of an input field. By having React manage all modifications, we guarantee that the visual component that the user is interacting with on the DOM matches the state of the React component behind the scenes.

The best way to understand this is to see what it looks like.

To make these input fields stateful, let’s first initialize state at the top of the component:

class TimerForm extends React.Component {
  state = {
    title: this.props.title || '',
    project: this.props.project || '',
  };

Our state object has two properties, each corresponding to an input field that TimerForm manages. We set the initial state of these properties to the values passed down via props. If TimerForm iscreating a new timer as opposed to editing an existing one, those props would be undefined. In that case, we initialize both to a blank string ( ‘ ‘ ).

This image has an empty alt attribute; its file name is image-447.pngWe want to avoid initializing title or project to undefined. That’s because the value of an input field can’t technically ever be undefined. If it’s empty, its value in JavaScript is a blank string. In fact, if you initialize the value of an input field to undefined, React will complain.

defaultValue only sets the value of the input field for the initial render. Instead of using defaultValue, we can connect our input fields directly to our component’s state using value. We could do something like this:

<div className='field'>
  <label>Title</label>
  <input
    type='text'
    value={this.state.title}
  />
</div>

With this change, our input fields would be driven by state. Whenever the state properties title or project change, our input fields would be updated to reflect the new value.

However, this misses a key ingredient: We don’t currently have any way for the user to modify this state. The input field will start off in-sync with the component’s state. But the moment the user makes a modification, the input field will become out-of-sync with the component’s state.

We can fix this by using React’s onChange attribute for input elements. Like onClick for button or a elements, we can set onChange to a function. Whenever the input field is changed, React will invoke the function specified.

Let’s set the onChange attributes on both input fields to functions we’ll define next:

         <div className='field'>
           <label>Title</label>
           <input
             type='text'
             value={this.state.title}
             onChange={this.handleTitleChange}
           />
         </div>
         <div className='field'>
           <label>Project</label>
           <input
             type='text'
             value={this.state.project}
             onChange={this.handleProjectChange}
           />
         </div>

The functions handleTitleChange and handleProjectChange will both modify their respective properties in state. Here’s what they look like:

handleTitleChange = (e) => {
  this.setState({ title: e.target.value });
};
  
handleProjectChange = (e) => {
  this.setState({ project: e.target.value });
};

When React invokes the function passed to onChange, it invokes the function with an event object. We call this argument e. The event object includes the updated value of the field under target.value. We update the state to the new value of the input field.

Using a combination of state, the value attribute, and the onChange attribute is the canonical method we use to write form elements in React. We explore forms in depth in the chapter “Forms.” We explore this topic specifically in the section “Uncontrolled vs. Controlled Components.”

To recap, here’s an example of the lifecycle of TimerForm:

  1. On the page is a timer with the title “Mow the lawn.”
  2. The user toggles open the edit form for this timer, mounting TimerForm to the page.
  3. TimerForm initializes the state property title to the string “Mow the lawn”.
  4. The user modifies the input field, changing it to the value “Cut the grass”.
  5. With every keystroke, React invokes handleTitleChange. The internal state of title is kept in-sync with what the user sees on the page.

With TimerForm refactored, we’ve finished establishing our stateful data inside our elected components. Our downward data pipeline, props, is assembled.

We’re ready — and perhaps a bit eager — to build out interactivity using inverse data flow. But before we do, let’s save and reload the app to ensure everything is working. We expect to see new example timers based on the hard-coded data in TimersDashboard. We also expect clicking the “+” button toggles open a form:

Step 6: Add inverse data flow

As we saw in the last chapter, children communicate with parents by calling functions that are handed to them via props. In the ProductHunt app, when an upvote was clicked Product didn’t do any data management. It was not the owner of its state. Instead, it called a function given to it by ProductList, passing in its id. ProductList was then able to manage state accordingly.

We are going to need inverse data flow in two areas:

  • TimerForm needs to propagate create and update events (create while under ToggleableTimerForm and update while under EditableTimer). Both events will eventually reach TimersDashboard.
  • Timer has a fair amount of behavior. It needs to handle delete and edit clicks, as well as the start and stop timer logic.

Let’s start with TimerForm.

TimerForm

To get a clear idea of what exactly TimerForm will require, we’ll start by adding event handlers to it and then work our way backwards up the hierarchy.

TimerForm needs two event handlers:

  • When the form is submitted (creating or updating a timer)
  • When the “Cancel” button is clicked (closing the form)

TimerForm will receive two functions as props to handle each event. The parent component that uses TimerForm is responsible for providing these functions:

  • props.onFormSubmit(): called when the form is submitted
  • props.onFormClose(): called when the “Cancel” button is clicked

As we’ll see soon, this empowers the parent component to dictate what the behavior should be when these events occur.

Let’s first modify the buttons on TimerForm. We’ll specify onClick attributes for each:

         <div className='ui two bottom attached buttons'>
           <button
             className='ui basic blue button'
             onClick={this.handleSubmit}
           >
             {submitText}
           </button>
           <button
             className='ui basic red button'
             onClick={this.props.onFormClose}
           >
             Cancel
           </button>
          </div>

The onClick attribute for the “Submit” button specifies the function this.handleSubmit, which we’ll define next. The onClick attribute for the “Cancel” button specifies the prop onFormClose directly.

Let’s see what handleSubmit looks like:

handleSubmit = () => {
  this.props.onFormSubmit({
    id: this.props.id,
    title: this.state.title,
    project: this.state.project,
  });
};

render() {

handleSubmit() calls a yet-to-be-defined function, onFormSubmit(). It passes in a data object with id, title, and project attributes. This means id will be undefined for creates, as no id exists yet.

Before moving on, let’s make one last tweak to TimerForm:

render() {
  const submitText = this.props.id ? 'Update' : 'Create';

We have submitText switch on id as opposed to title. Using the id property to determine whether or not an object has been created is a more common practice.

ToggleableTimerForm

Let’s chase the submit event from TimerForm as it bubbles up the component hierarchy. First, we’ll modify ToggleableTimerForm. We need it to pass down two prop-functions to TimerForm, onFormClose() and onFormSubmit():

// Inside ToggleableTimerForm
handleFormOpen = () => {
  this.setState({ isOpen: true });
};

handleFormClose = () => {
  this.setState({ isOpen: false });
};

handleFormSubmit = (timer) => {
  this.props.onFormSubmit(timer);
  this.setState({ isOpen: false });
};

render() {
  if (this.state.isOpen) {
    return (
      <TimerForm
        onFormSubmit={this.handleFormSubmit}
        onFormClose={this.handleFormClose}
      />
    );
  } else {

Looking first at the render() function, we can see we pass in the two functions as props. Functions are just like any other prop.

Of most interest here is handleFormSubmit(). Remember, ToggleableTimerForm is not the manager of timer state. TimerForm has an event it’s emitting, in this case the submission of a new timer. ToggleableTimerForm is just a proxy of this message. So, when the form is submitted, it calls its own prop-function props.onFormSubmit(). We’ll eventually define this function in TimersDashboard.

handleFormSubmit() accepts the argument timer. Recall that in TimerForm this argument is an object containing the desired timer properties. We just pass that argument along here.

After invoking onFormSubmit(), handleFormSubmit() calls setState() to close its form.

Note that the result of onFormSubmit() will not impact whether or not the form is closed. We invoke onFormSubmit(), which may eventually create an asynchronous call to a server. Execution will continue before we hear back from the server which means setState() will be called.

If onFormSubmit() fails — such as if the server is temporarily unreachable — we’d ideally have some way to display an error message and re-open the form.

TimersDashboard

We’ve reached the top of the hierarchy, TimersDashboard. As this component will be responsible for the data for the timers, it is here that we will define the logic for handling the events we’re capturing down at the leaf components.

The first event we’re concerned with is the submission of a form. When this happens, either a new timer is being created or an existing one is being updated. We’ll use two separate functions to handle the two distinct events:

  • handleCreateFormSubmit() will handle creates and will be the function passed to ToggleableTimerForm
  • handleEditFormSubmit() will handle updates and will be the function passed to EditableTimerList

Both functions travel down their respective component hierarchies until they reach TimerForm as the prop onFormSubmit().

Let’s start with handleCreateFormSubmit, which inserts a new timer into our timer list state:

// Inside TimersDashboard
handleCreateFormSubmit = (timer) => {
  this.createTimer(timer);
};

createTimer = (timer) => {
  const t = helpers.newTimer(timer);
  this.setState({
    timers: this.state.timers.concat(t),
  });
};

render() {
  return (
    <div className='ui three column centered grid'>
      <div className='column'>
        <EditableTimerList
          timers={this.state.timers}
        />
        <ToggleableTimerForm
          onFormSubmit={this.handleCreateFormSubmit}
        />
      </div>
     </div>
   );
 }

We create the timer object with helpers.newTimer(). You can peek at the implementation inside of helpers.js. We pass in the object that originated down in TimerForm. This object has title and project properties. helpers.newTimer() returns an object with those title and project properties as well as a generated id.

The next line calls setState(), appending the new timer to our array of timers held under timers. We pass the whole state object to setState().

You might wonder: why separate handleCreateFormSubmit() and createTimer()? While not strictly required, the idea here is that we have
one function for handling the event (handleCreateFormSubmit()) and another for performing the operation of creating a timer (createTimer()).

This separation follows from the Single Responsibility Principle and enables us to call createTimer() from elsewhere if needed.

We’ve finished wiring up the create timer flow from the form down in TimerForm up to the state managed in TimersDashboard. Save app.js and reload your browser. Toggle open the create form and create some new timers:

Updating timers

We need to give the same treatment to the update timer flow. However, as you can see in the current state of the app, we haven’t yet added the ability for a timer to be edited. So we don’t have a way to display an edit form, which will be a prerequisite to submitting one.

To display an edit form, the user clicks on the edit icon on a Timer. This should propagate an event up to EditableTimer and tell it to flip its child component, opening the form.

Adding editability to Timer

To notify our app that the user wants to edit a timer we need to add an onClick attribute to the span tag of the edit button. We anticipate a prop-function, onEditClick():

{/* Inside Timer.render() */}
<div className='extra content'>
  <span
    className='right floated edit icon'
    onClick={this.props.onEditClick}
  >
    <i className='edit icon' />
  </span>
  <span className='right floated trash icon'>
    <i className='trash icon' />
  </span>
</div>

Updating EditableTimer

Now we’re prepared to update EditableTimer. Again, it will display either the TimerForm (if we’re editing) or an individual Timer (if we’re not editing).

Let’s add event handlers for both possible child components. For TimerForm, we want to handle the form being closed or submitted. For Timer, we want to handle the edit icon being pressed:

// Inside EditableTimer
handleEditClick = () => {
  this.openForm();
};

handleFormClose = () => {
  this.closeForm();
};

handleSubmit = (timer) => {
  this.props.onFormSubmit(timer);
  this.closeForm();
};
 
closeForm = () => {
  this.setState({ editFormOpen: false });
};

openForm = () => {
  this.setState({ editFormOpen: true });
};

We pass these event handlers down as props:

render() {
  if (this.state.editFormOpen) {
    return (
      <TimerForm
        id={this.props.id}
        title={this.props.title}
        project={this.props.project}
        onFormSubmit={this.handleSubmit}
        onFormClose={this.handleFormClose}
      />
    );
  } else {
    return (
      <Timer
        id={this.props.id}
        title={this.props.title}
        project={this.props.project}
        elapsed={this.props.elapsed}
        runningSince={this.props.runningSince}
        onEditClick={this.handleEditClick}
      />
    );
  }
}

Look a bit familiar? EditableTimer handles the same events emitted from TimerForm in a very similar manner as ToggleableTimerForm. This makes sense. Both EditableTimer and ToggleableTimerForm are just intermediaries between TimerForm and TimersDashboard.
TimersDashboard is the one that defines the submit function handlers and assigns them to a given component tree.

Like ToggleableTimerForm, EditableTimer doesn’t do anything with the incoming timer. In handleSubmit(), it just blindly passes this object along to its prop-function onFormSubmit(). It then closes the form with closeForm().

We pass along a new prop to Timer, onEditClick. The behavior for this function is defined in handleEditClick, which modifies the state for EditableTimer, opening the form.

Updating EditableTimerList

Moving up a level, we make a one-line addition to EditableTimerList to send the submit function from TimersDashboard to each EditableTimer:

// Inside EditableTimerList
const timers = this.props.timers.map((timer) => (
  <EditableTimer
    key={timer.id}
    id={timer.id}
    title={timer.title}
    project={timer.project}
    elapsed={timer.elapsed}
    runningSince={timer.runningSince}
    onFormSubmit={this.props.onFormSubmit}
  />
));
// ...

EditableTimerList doesn’t need to do anything with this event so again we just pass the function on directly.

Defining onEditFormSubmit() in TimersDashboard

Last step with this pipeline is to define and pass down the submit function for edit forms in TimersDashboard.
For creates, we have a function that creates a new timer object with the specified attributes and we append this new object to the end of the timers array in the state.
For updates, we need to hunt through the timers array until we find the timer object that is being updated. As mentioned in the last chapter, the state object cannot be updated directly. We have to use setState().
Therefore, we’ll use map() to traverse the array of timer objects. If the timer’s id matches that of the form submitted, we’ll return a new object that contains the timer with the updated attributes. Otherwise we’ll just return the original timer. This new array of timer objects will be passed to setState():

// Inside TimersDashboard
handleEditFormSubmit = (attrs) => {
  this.updateTimer(attrs);
};

createTimer = (timer) => {
  const t = helpers.newTimer(timer);
  this.setState({
    timers: this.state.timers.concat(t),
  });
};

updateTimer = (attrs) => {
  this.setState({
    timers: this.state.timers.map((timer) => {
      if (timer.id === attrs.id) {
        return Object.assign({}, timer, {
          title: attrs.title,
          project: attrs.project,
        });
      } else {
        return timer;
      }
    }),
  });
};

We pass this down as a prop inside render():

{ /* Inside TimersDashboard.render() */}
<EditableTimerList
  timers={this.state.timers}
  onFormSubmit={this.handleEditFormSubmit}
/>

Note that we can call map() on this.state.timers from within the JavaScript object we’re passing to setState(). This is an often used pattern. The call is evaluated and then the property timers is set to the result.

Inside of the map() function we check if the timer matches the one being updated. If not, we just return the timer. Otherwise, we use Object#assign() to return a new object with the timer’s updated attributes.

Remember, it’s important here that we treat state as immutable. By creating a new timers object and then using Object#assign() to populate it, we’re not modifying any of the objects sitting in state.

We discuss the Object#assign() method in the last chapter.

As we did with ToggleableTimerForm and handleCreateFormSubmit, we pass down handleEditFormSubmit as the prop onFormSubmit. TimerForm calls this prop, oblivious to the fact that this function is entirely different when it is rendered underneath EditableTimer as opposed to ToggleableTimerForm.

Both of the forms are wired up! Save app.js, reload the page, and try both creating and updating timers. You can also click “Cancel” on an open form to close it:

The rest of our work resides within the timer. We need to:

  • Wire up the trash button (deleting a timer)
  • Implement the start/stop buttons and the timing logic itself

At that point, we’ll have a complete server-less solution.

Try it yourself: Before moving on to the next section, see how far you can get wiring up the trash button by yourself. Move ahead afterwards and verify your solution is sound.

Deleting timers

Adding the event handler to Timer

In Timer, we define a function to handle trash button click events:

class Timer extends React.Component {
  handleTrashClick = () => {
    this.props.onTrashClick(this.props.id);
};

render() {

And then use onClick to connect that function to the trash icon:

{/* Inside Timer.render() */}
<div className='extra content'>
  <span
    className='right floated edit icon'
    onClick={this.props.onEditClick}
  >
    <i className='edit icon' />
  </span>
  <span
    className='right floated trash icon'
    onClick={this.handleTrashClick}
  >
    <i className='trash icon' />
  </span>
</div>

We’ve yet to define the function that will be set as the prop onTrashClick(). But you can imagine that when this event reaches the top (TimersDashboard), we’re going to need the id to sort out which timer is being deleted. handleTrashClick() provides the id to this function.

Routing through EditableTimer

EditableTimer just proxies the function:

// Inside EditableTimer
} else {
  return (
    <Timer
      id={this.props.id}
      title={this.props.title}
      project={this.props.project}
      elapsed={this.props.elapsed}
      runningSince={this.props.runningSince}
      onEditClick={this.handleEditClick}
      onTrashClick={this.props.onTrashClick}
    />
  );
}

Routing through EditableTimerList

As does EditableTimerList:

// Inside EditableTimerList.render()
const timers = this.props.timers.map((timer) => (
  <EditableTimer
    key={timer.id}
    id={timer.id}
    title={timer.title}
    project={timer.project}
    elapsed={timer.elapsed}
    runningSince={timer.runningSince}
    onFormSubmit={this.props.onFormSubmit}
    onTrashClick={this.props.onTrashClick}
  />
));

Implementing the delete function in TimersDashboard

The last step is to define the function in TimersDashboard that deletes the desired timer from the state array. There are many ways to accomplish this in JavaScript. Don’t sweat it if your solution was not the same or if you didn’t quite work one out.

We add our handler function that we ultimately pass down as a prop:

// Inside TimersDashboard
handleEditFormSubmit = (attrs) => {
  this.updateTimer(attrs);
};

handleTrashClick = (timerId) => {
  this.deleteTimer(timerId);
};

deleteTimer() uses Array’s filter() method to return a new array with the timer object that has an id matching timerId removed:

// Inside TimersDashboard
deleteTimer = (timerId) => {
  this.setState({
    timers: this.state.timers.filter(t => t.id !== timerId),
  });
};

Finally, we pass down handleTrashClick() as a prop:

{/* Inside TimersDashboard.render() */}
<EditableTimerList
  timers={this.state.timers}
  onFormSubmit={this.handleEditFormSubmit}
  onTrashClick={this.handleTrashClick}
/>

Array’s filter() method accepts a function that is used to “test” each element in the array. It returns a new array containing all the elements
that “passed” the test. If the function returns true, the element is kept.

Save app.js and reload the app. Now you can delete timers:

Adding timing functionality

Create, update, and delete (CRUD) capability is now in place for our timers. The next challenge: making these timers functional.

There are several different ways we can implement a timer system. The simplest approach would be to have a function update the elapsed property on each timer every second. But this is severely limited. What happens when the app is closed? The timer should continue “running.”

This is why we’ve included the timer property runningSince. A timer is initialized with elapsed equal to 0. When a user clicks “Start”, we do not increment elapsed. Instead, we just set runningSince to the start time.

We can then use the difference between the start time and the current time to render the time for the user. When the user clicks “Stop”, the difference between the start time and the current time is added to elapsed. runningSince is set to null.

Therefore, at any given time, we can derive how long the timer has been running by taking Date.now() – runningSince and adding it to the total accumulated time (elapsed). We’ll calculate this inside the Timer component.

For the app to truly feel like a running timer, we want React to constantly perform this operation and re-render the timers. But elapsed and runningSince will not be changing while the timer is running. So the one mechanism we’ve seen so far to trigger a render() call will not be sufficient.

Instead, we can use React’s forceUpdate() method. This forces a React component to re-render. We can call it on an interval to yield the smooth appearance of a live timer.

Adding a forceUpdate() interval to Timer

helpers.renderElapsedString() accepts an optional second argument, runningSince. It will add the delta of Date.now() – runningSince to elapsed and use the function millisecondsToHuman() to return a string formatted as HH:MM:SS.

We will establish an interval to run forceUpdate() after the component mounts:

class Timer extends React.Component {
  componentDidMount() {
    this.forceUpdateInterval = setInterval(() => this.forceUpdate(), 50\
);
  }

  componentWillUnmount() {
    clearInterval(this.forceUpdateInterval);
  }

handleTrashClick = () => {
  this.props.onTrashClick(this.props.id);
};

render() {
  const elapsedString = helpers.renderElapsedString(
    this.props.elapsed, this.props.runningSince
  );
  return (

In componentDidMount(), we use the JavaScript function setInterval(). This will invoke the function forceUpdate() once every 50 ms, causing the component to rerender. We set the return of setInterval() to this.forceUpdateInterval.

In componentWillUnmount(), we use clearInterval() to stop the interval this.forceUpdateInterval. componentWillUnmount() is called before a component is removed from the app. This will happen if a timer is deleted. We want to ensure we do not continue calling forceUpdate() after the timer has been removed from the page. React will throw errors.

setInterval() accepts two arguments. The first is the function you would like to call repeatedly. The second is the interval on which to call that function (in milliseconds).

setInterval() returns a unique interval ID. You can pass this interval ID to clearInterval() at any time to halt the interval.

You might ask: Wouldn’t it be more efficient if we did not continuously call forceUpdate() on timers that are not running?

Indeed, we would save a few cycles. But it would not be worth the added code complexity. React will call render() which performs some
inexpensive operations in JavaScript. It will then compare this result to the previous call to render() and see that nothing has changed. It stops there — it won’t attempt any DOM manipulation.

The 50 ms interval was not derived scientifically. Selecting an interval that’s too high will make the timer look unnatural. It would jump unevenly between values. Selecting an interval that’s too low would just be an unnecessary amount of work. A 50 ms interval looks good to humans and is ages in computerland.

Try it out

Save app.js and reload. The first timer should be running.

We’ve begun to carve out the app’s real utility! We need only wire up the start/stop button and our server-less app will be feature complete.

Add start and stop functionality

The action button at the bottom of each timer should display “Start” if the timer is paused and “Stop” if the timer is running. It should also propagate events when clicked, depending on if the timer is being stopped or started.

We could build all of this functionality into Timer. We could have Timer decide to render one HTML snippet or another depending on if it is running. But that would be adding more responsibility and complexity to Timer. Instead, let’s make the button its own React component.

Add timer action events to Timer

Let’s modify Timer, anticipating a new component called TimerActionButton. This button just needs to know if the timer is running. It also needs to be able to propagate two events, onStartClick() and onStopClick(). These events will eventually need to make it all the way up to TimersDashboard, which can modify runningSince on the timer.

First, the event handlers:

// Inside Timer
componentWillUnmount() {
  clearInterval(this.forceUpdateInterval);
}

handleStartClick = () => {
  this.props.onStartClick(this.props.id);
};

handleStopClick = () => {
  this.props.onStopClick(this.props.id);
};
// ...

Then, inside render(), we’ll declare TimerActionButton at the bottom of the outermost div:

          {/* At the bottom of `Timer.render()`` */}
          <TimerActionButton
            timerIsRunning={!!this.props.runningSince}
            onStartClick={this.handleStartClick}
            onStopClick={this.handleStopClick}
          />
        </div>
      );

We use the same technique used in other click-handlers: onClick on the HTML element specifies a handler function in the component that invokes a prop-function, passing in the timer’s id.

We use !! here to derive the boolean prop timerIsRunning for TimerActionButton. !! returns false when runningSince is null.

Create TimerActionButton

Create the TimerActionButton component now:

class TimerActionButton extends React.Component {
  render() {
    if (this.props.timerIsRunning) {
      return (
        <div
          className='ui bottom attached red basic button'
          onClick={this.props.onStopClick}
        >
      
          Stop
        </div>
      );
    } else {
      return (
        <div
          className='ui bottom attached green basic button'
          onClick={this.props.onStartClick}
        >
          Start
        </div>
      );
    }
  }
}

We render one HTML snippet or another based on this.props.timerIsRunning.

You know the drill. Now we run these events up the component hierarchy, all the way up to TimersDashboard where we’re managing state:

Run the events through EditableTimer and EditableTimerList

First EditableTimer:

  // Inside EditableTimer
} else {
  return (
    <Timer
      id={this.props.id}
      title={this.props.title}
      project={this.props.project}
      elapsed={this.props.elapsed}
      runningSince={this.props.runningSince}
      onEditClick={this.handleEditClick}
      onTrashClick={this.props.onTrashClick}
      onStartClick={this.props.onStartClick}
      onStopClick={this.props.onStopClick}
    />
  );
}

And then EditableTimerList:

// Inside EditableTimerList
const timers = this.props.timers.map((timer) => (
  <EditableTimer
    key={timer.id}
    id={timer.id}
    title={timer.title}
    project={timer.project}
    elapsed={timer.elapsed}
    runningSince={timer.runningSince}
    onFormSubmit={this.props.onFormSubmit}
    onTrashClick={this.props.onTrashClick}
    onStartClick={this.props.onStartClick}
    onStopClick={this.props.onStopClick}
  />
));

Finally, we define these functions in TimersDashboard. They should hunt through the state timers array using map, setting runningSince appropriately when they find the matching timer.

First we define the handling functions:

// Inside TimersDashboard
handleTrashClick = (timerId) => {
  this.deleteTimer(timerId);
};
handleStartClick = (timerId) => {
  this.startTimer(timerId);
};
handleStopClick = (timerId) => {
  this.stopTimer(timerId);
};

And then startTimer() and stopTimer():

deleteTimer = (timerId) => {
  this.setState({
    timers: this.state.timers.filter(t => t.id !== timerId),
  });
};

startTimer = (timerId) => {
  const now = Date.now();

  this.setState({
    timers: this.state.timers.map((timer) => {
      if (timer.id === timerId) {
        return Object.assign({}, timer, {
          runningSince: now,
        });
      } else {
        return timer;
      }
    }),
  });
};

stopTimer = (timerId) => {
  const now = Date.now();

  this.setState({
    timers: this.state.timers.map((timer) => {
      if (timer.id === timerId) {
        const lastElapsed = now - timer.runningSince;
        return Object.assign({}, timer, {
          elapsed: timer.elapsed + lastElapsed,
          runningSince: null,
        });
      } else {
        return timer;
        }
      }),
    });
  };

Finally, we pass them down as props:

{/* Inside TimerDashboard.render() */}
<EditableTimerList
  timers={this.state.timers}
  onFormSubmit={this.handleEditFormSubmit}
  onTrashClick={this.handleTrashClick}
  onStartClick={this.handleStartClick}
  onStopClick={this.handleStopClick}
/>

When startTimer comes across the relevant timer within its map call, it sets the property runningSince to the current time.

stopTimer calculates lastElapsed, the amount of time that the timer has been running for since it was started. It adds this amount to elapsed and sets runningSince to null, “stopping” the timer.

Try it out

Save app.js, reload, and behold! You can now create, update, and delete timers as well as actually use them to time things:

This is excellent progress. But, without a connection to a server, our app is ephemeral. If we refresh the page, we lose all of our timer data. Our app does not have any persistence.

A server can give us persistence. We’ll have our server write all changes to timer data to a file. Instead of hard-coding state inside of the TimersDashboard component, when our app loads it will query the server and construct its timer state based on the data the server provides. We’ll then have our React app notify the server about any state changes, like when a timer is started.

Communicating with a server is the last big major building block you’ll need to develop and distribute real-world web applications with React.

Methodology review

While building our time-logging app, we learned and applied a methodology for building React apps. Again, those steps were:

  1. Break the app into components
    • We mapped out the component structure of our app by examining the app’s working UI. We then applied the single-responsibility principle to break components down so that each had minimal viable functionality.
  2. Build a static version of the app
    • Our bottom-level (user-visible) components rendered HTML based on static props, passed down from parents.
  3. Determine what should be stateful
    • We used a series of questions to deduce what data should be stateful. This data was represented in our static app as props.
  4. Determine in which component each piece of state should live
    • We used another series of questions to determine which component should own each piece of state. TimersDashboard owned timer state data and ToggleableTimerForm and EditableTimer both held state pertaining to whether or not to render a TimerForm.
  5. Hard-code initial states
    • We then initialized state-owners’ state properties with hard-coded values.
  6. Add inverse data flow
    • We added interactivity by decorating buttons with onClick handlers. These called functions that were passed in as props down the hierarchy from whichever component owned the relevant state being manipulated.

The final step is 7. Add server communication. We’ll tackle this in the next chapter.