Evolving JavaScript Part 3: Building a React Application

In the previous posts, we covered why we moved away from Backbone and started building new features with React. To further reinforce why we invested in our migration from Backbone to React, we've built out a tutorial that demonstrates some of the UI principles we follow here at Gusto. We'll be creating an employee directory application together utilizing common React patterns, and below you'll find a link to a GitHub repo that will allow you to follow along.

https://github.com/Gusto/gusto-react-tutorial

App setup

To streamline the tutorial and focus on building React components, this app has been setup with a variety of tools to get you up and running.

  • Webpack
    • Loaders are provided to parse ES6 (and a handful of ES7 properties), JSX, CSS, and images.
    • The ES6 module system is used for importing and exporting code.
    • Hot loading for the React components has been configured.
    • The webpack-dev-server is used for maximum development speed.
  • ESLint
    • We've adopted AirBnB's style guide (with a handful of modifications).
    • I highly recommend you configure your editor to live lint if you have not already.
  • Karma, Mocha, Chai, Sinon, Enzyme
    • Karma is setup as our test runner and you can run the whole test suite using npm test.
    • You can also start a test server that will run as you change files with npm run test-server (recommended).
  • Node API
    • A small API serving a list of employees can be found at localhost:8080/api/employees.
    • The API will start up along the webpack-dev-server when running npm start.

Getting Started

The repo that was provided was designed to follow along with this tutorial and includes everything you need to build the React application. The master branch will ultimately represent the finished product that we will build, so start by running git checkout step-0 to see the initial setup and follow along with the post!

Provisioning

  • Ensure that you have the latest version of node and npm installed. You can install these view Homebrew with brew install node.
  • Install the dependent JS packages with npm install.
  • Start the server with npm start and you should see a "Hello, World!" screen.
  • If you haven't already, download the React DevTools for a powerful in-browser debugger that will further illustrate the concepts we will be covering.

Repo Overview

All of the code we will be modifying for the scope of this tutorial will live in the build/js directory. In here you will notice an index.js file that will import and start the app, as well as a clients and components directory.

Clients

These JavaScript classes provide a small abstraction for interfacing with our API. The build/js/clients/employees_client.js file contains an EmployeesClient class for interfacing with the server as well as an Immutable Employee record that defines the schema we expect for employee data. EmployeesClient exposes a method called all which will fetch all employees from the API, wrap the result in a collection of Immutable records, and return a promise to handle the response from within the component.

The Employee constructor utilizes Immutable's record functionality, which enforces that the structure of the data matches what we would expect. This is powerful because it allows us to create a single definition for what an employee should look like and validate that the structure is maintained throughout the app as well as ensure that any instances of this data will never be mutated.

Elements

Similar to what was described in our own app, these "element" React components will represent a few small building blocks of the application that we can use for abstracting away common functionality. You will notice things like a Page and a Loader component and you can treat these as parts of the user experience that are flexible enough for use across an application. If we were to expand on the functionality we build today, these could be utilized to create a consistent experience as well as provide a single place to update these building blocks if they were to change.

Displaying Employee Data

To get started building our employee directory, the first thing we will do is fetch some data about our employees and display it on the DOM. In order to do that, we'll create what we call a container component for fetching data. This article details a common pattern for abstracting data management away from display logic while using React. The author quotes:

A container does data fetching and then renders its corresponding sub-component. That’s it.

This pattern allows for components to be more reusable, since they are not tied to retrieving the data. We will write a very thin layer that handles interfacing with the server and passing the data as props to a component that we want to render it. This will allow that component to receive the data from anywhere and still be able to render it accordingly.

Let's start by creating a component called the DirectoryContainer that will use our EmployeesClient helper to fetch the data from the server and provide us a collection of Immutable Employee records to render onto the DOM. While the data is being fetched from the server, let's use the Loader component to give the user some feedback that their data is on the way, and when the data is loaded, let's temporarily print the number of employees to see that the API request was successful.

build/js/components/employees/directory_container.jsx
import React, { Component } from 'react';  
import EmployeesClient from 'js/clients/employees_client';

import Loader from '../elements/loader';

export default class DirectoryContainer extends Component {  
  state = { loading: true, employees: [] };

  componentWillMount() {
    EmployeesClient.all().then((employees) => {
      this.setState({ loading: false, employees });
    });
  }

  render() {
    return (
      <Loader loading={this.state.loading}>
        {this.state.employees.length}
      </Loader>
    );
  }
}

To see this in action, let's modify our App component to display this DirectoryContainer and wrap it in a Page component to ensure it is styled properly.

build/js/components/app.jsx
import React, { Component } from 'react';

import Page from './elements/page';  
import DirectoryContainer from './employees/directory_container';

export default class App extends Component {  
  render() {
    return (
      <Page>
        <DirectoryContainer />
      </Page>
    );
  }
}

Now that our component is mounted in the App, you should see a loader appear and then display number in the top left of the page when the request is complete. Nice!

In the DirectoryContainer, we start by defining an initial state using the ES7 class properties syntax. This state definition will get evaluated in the constructor during instantiation time and will serve as the initial value of our component's state before our fetching is complete. Notice how we defined an empty array as the default value for the employees key in the state of the component - this will allow the component to render without errors before the data is fetched by preventing calling length of undefined (this.state.employees) in the render method.

By looking at the Loader class, we can quickly see from the propTypes that the component takes a loading boolean and children - it will show a spinner if the loading prop is true and display the children if it is false. Accordingly, we define our DirectoryContainer component to be in the loading state by default and pass our content as children to the Loader so that it will display them when loading is complete.

We then hook into our DirectoryContainer's componentWillMount lifecycle method, allowing us to execute an API call before the component is mounted. Once we get a response from the server, we set the employees into the state of the component and set loading to false, which will trigger a re-render, hide the loader, and show the number of employees.

If you open up the React DevTools and search for the DirectoryContainer component, you'll notice that in the state there is an array of Immutable records that represent our employees. They have a few attributes, first_name, last_name, and email (defined in build/js/clients/employees_client), so let's create a component that will be able to render this array of employees and display them each individually as a part of a list. This will be our Directory component and it will get it's data from the DirectoryContainer.

build/js/components/employees/directory.jsx
import React, { Component, PropTypes } from 'react';  
import { Employee } from 'js/clients/employees_client';

export default class Directory extends Component {  
  static propTypes = {
    employees: PropTypes.arrayOf(PropTypes.instanceOf(Employee)).isRequired
  }

  _renderEmployee(employee) {
    return (
      <li key={employee.email}>
        <div><strong>First Name:</strong> {employee.first_name}</div>
        <div><strong>Last Name:</strong> {employee.last_name}</div>
        <div><strong>Email:</strong> {employee.email}</div>
      </li>
    );
  }

  render() {
    return (
      <ul>
        {this.props.employees.map(this._renderEmployee)}
      </ul>
    );
  }
}

And then of course, let's switch out our number display to instead render our new directory of employees.

build/js/components/employees/directory_container.jsx
import React, { Component } from 'react';  
...
import Directory from './directory';

export default class DirectoryContainer extends Component {  
  ...
  render() {
    return (
      <Loader loading={this.state.loading}>
        <Directory employees={this.state.employees} />
      </Loader>
    );
  }
}

Now, you should be able to see a list of employees within the app!

Since our render function is just plain ol' JavaScript, we simply map over the list of employees and return a JSX element for each employee in the list. You'll notice we added a key prop to each li - this is part of how React does it's internal diff'ing. It needs an unique identifier for each sibling of the same type in the tree so it can properly resolve what needs to be updated. Using an index is an anti-pattern here, so always provide a unique value that can be used by React to optimize DOM updates.

In our Directory component, notice how we were able to strictly define not only that we should receive an employees prop but that it should be an instance of the Employee Immutable record we described in the beginning. This is really powerful, not only have we designed a composable component that can be used in a variety of contexts, but we actually validate that it gives us all the data we need to render the employee and ensure that our component will render as expected. Huzzah!

Testing Components

Testing is a crucial part of our development at Gusto, and our frontend code is no different. The introduction of React has made testing more enjoyable as we write our code and the fast test execution makes for great feedback when developing. The repo has a full test suite included with it, but we will highlight just a few examples here to demonstrate the point.

Background

React ships with its own set of Test Utilities, but we as team have found these clunky and difficult to use. We have opted to use AirBnB's Enzyme which will allow us to use a more intuitive API for asserting component correctness. Also look out for the different types of rendering in order to optimally mount the component based on each test's needs.

If you open up a new terminal and run npm test, you'll notice that we have broken our App test because it no longer greets the world! Let's update this spec to fix it.

Pro tip: from this point on, use npm run test-server to watch for file changes and automatically have the test suite run on update.

spec/components/app_spec.jsx
import React from 'react';

import App from 'js/components/app';  
import { shallow } from 'enzyme';

import DirectoryContainer from 'js/components/employees/directory_container';  
import Page from 'js/components/elements/page';

describe('App', function() {  
  beforeEach(function() {
    this.spec.component = shallow(<App />);
  });

  it('renders a Page', function() {
    expect(this.spec.component.find(Page)).to.have.length(1);
  });

  it('renders the DirectoryContainer within the Page', function() {
    expect(this.spec.component.find(Page).find(DirectoryContainer)).to.have.length(1);
  });
});

Here, we will very simply assert that we have rendered a Page component with a DirectoryContainer inside. Notice here that we will use the shallow render method provided to use by Enzyme - meaning that the DirectoryContainer will not actually be mounted and attempt to fetch employees, but instead will render a stub for the sake of the assertions.

Next let's test that our DirectoryContainer component starts in the proper state and accordingly shows the Loader before rendering the Directory.

spec/components/employees/directorycontainerspec.jsx
import React from 'react';

import DirectoryContainer from 'js/components/employees/directory_container';  
import { mount } from 'enzyme';

import Loader from 'js/components/elements/loader';  
import Directory from 'js/components/employees/directory';  
import EmployeesClient, { Employee } from 'js/clients/employees_client';

describe('DirectoryContainer', function() {  
  context('before the API request is complete', function() {
    beforeEach(function() {
      this.spec.promise = new Promise(function() {});
      this.spec.sandbox.stub(EmployeesClient, 'all').returns(this.spec.promise);
      this.spec.component = mount(<DirectoryContainer />);
    });

    it('starts in the loading state with no employees', function() {
      expect(this.spec.component.state('loading')).to.be.true;
      expect(this.spec.component.state('employees')).to.have.length(0);
    });

    it('passes loading=true to the Loader', function() {
      expect(this.spec.component.find(Loader).prop('loading')).to.be.true;
      expect(this.spec.component.find(Directory)).to.have.length(0);
    });
  });
});

We use Sinon to stub our EmployeesClient method with a promise that we can control from the test (this.spec.sandbox is setup in spec_helper.js - it simply cleans up all Sinon stubs after each test).

By doing so, we have the component in a loading state and can test that the state is passed down to the Loader and the Directory is accordingly not yet rendered. Finally, let's test that when the API request is complete, it passes the data down to the Directory.

spec/components/employees/directorycontainerspec.jsx
import React from 'react';  
...
describe('DirectoryContainer', function() {  
  ...
  context('when the API request is complete', function() {
    beforeEach(function() {
      this.spec.employees = [
        new Employee({ first_name: 'Bob', last_name: 'Bobberson', email: 'bob@bob.com' })
      ];
      this.spec.promise = Promise.resolve(this.spec.employees);
      this.spec.sandbox.stub(EmployeesClient, 'all').returns(this.spec.promise);
      this.spec.component = mount(<DirectoryContainer />);
    });

    it('is no longer in the loading state', function() {
      this.spec.promise.then(() => {
        expect(this.spec.component.state('loading')).to.be.false;
        expect(this.spec.component.state('employees')).to.have.length(1);
      });
    });

    it('renders the Directory with the employees', function() {
      this.spec.promise.then(() => {
        expect(this.spec.component.find(Directory)).to.have.length(1);
        expect(this.spec.component.find(Directory).prop('employees')).to.eql(this.spec.employees);
      });
    });
  });
});

In order to simulate the API request being complete, we resolve the promise and assert that the callback was successfully invoked - stopping the loader and setting the employees into state. We have to wrap our assertions in the promise to ensure the execution order is correct and that the assertions will be made after the component has updated. Finally, we test that we properly render the Directory and give the right props.

Adding Search Capabilities

Now that we have built out the basic directory functionality, let's see how we can extend this by adding a search bar to filter the employees. We will filter out the records on the frontend to simplify the example, but this can easily be modified to create an AJAX driven search.

You can get all the code needed to complete this portion of the tutorial with git checkout step-1.

Based on the way we have encapsulated our components using the container pattern, it should be simple to filter down the employees being displayed using a higher-order component. Let's create an abstraction on the Directory called the SearchableDirectory that will filter employees based on the search term.

build/js/components/employees/searchable_directory.jsx
import React, { Component, PropTypes } from 'react';  
import { Employee } from 'js/clients/employees_client';

import TextInput from '../elements/text_input.jsx';  
import Directory from './directory';

export default class SearchableDirectory extends Component {  
  static propTypes = {
    employees: PropTypes.arrayOf(PropTypes.instanceOf(Employee)).isRequired
  }

  state = { search: '' };

  _onSearch = (event) => {
    this.setState({ search: event.target.value });
  }

  _matchesSearch(attribute) {
    return attribute.toLowerCase().indexOf(this.state.search.toLowerCase()) >= 0;
  }

  _filteredEmployees() {
    if (!this.state.search) { return this.props.employees; }

    return this.props.employees.filter((employee) =>
      this._matchesSearch(employee.first_name) ||
        this._matchesSearch(employee.last_name) ||
        this._matchesSearch(employee.email)
    );
  }

  render() {
    return (
      <div>
        <TextInput value={this.state.search} onChange={this._onSearch} />
        <Directory employees={this._filteredEmployees()} />
      </div>
    );
  }
}

Finally, let's drop in SearchableDirectory instead of the Directory in the DirectoryContainer.

build/js/components/employees/directory_container.jsx
import React, { Component } from 'react';  
...
import SearchableDirectory from './searchable_directory';

export default class DirectoryContainer extends Component {  
  ...
  render() {
    return (
      <Loader loading={this.state.loading}>
        <SearchableDirectory employees={this.state.employees} />
      </Loader>
    );
  }
}

And our search is up and working!

Notice that because we used the container pattern to encapsulate the directory, it was trivial to add a higher level component to filter out the employees before the directory rendered them. This kind of reusability and maintainability is where React really shines.

In the SearchableDirectory component, we start by defining our propTypes (an array of Employee records), and an initial search state of ''. We create a _onSearch handler and assign it using the fat arrow to ensure the function is bound to the right context and can access the state of the SearchableDirectory, even when passed down to the TextInput. We have created a controlled component that will update the state and receive its new value to display with each keystroke. This method of data binding makes things like live search really easy, because the component will now update with each letter that has been typed.

Since we know that this.state.search will be updated whenever needed, we wrote a _filteredEmployees function that will return only the employees that match the search criteria. The result is passed to the Directory and they are displayed as needed, live updating with every keystroke!

Conclusion

Together we were able to build a simple application to fetch data from an API, show the results on the DOM, and test it end to end with little friction. More importantly, we were able to easily refactor and add functionality to search through the employee without having to change any of the components we had written originally, a testament to how well we encapsulated our code. Our components made use of the state and prop data binding for creating declarative views that live updated in response to user input.

We used Immutable's record functionality for defining a data schema and enforcing the structure through the app with prop types. We utilized shallow rendering in our tests where applicable and Enzyme's wrapper API for easy to read tests and assertions. Ultimately, we were able to easily build a small but well written application, made possible by the ecosystem that React has created and how it has changed how we think about frontend systems.

Here at Gusto, React has not only changed the way we think about writing views but has shaped how we build user experiences altogether. The switch from Backbone from React has paid great dividends for our team and we continue to work towards refining our patterns and tooling.

React is phenomenal library for building UI. Combined with tools like Webpack and patterns like Flux, it only becomes even more powerful. Webpack and Flux each warrant their own deep dive, and as promised, we will be publishing more content on how we moved off the Rails Asset Pipeline in favor of Webpack (reducing our development time significantly) as well as how we have used the Flux architecture to bridge the gap between Backbone and our server.

We recently hosted a tech talk (thanks for all who attended, looking forward to seeing you at the next one!), covering several of these concepts and the journey we took to get there. You can see the video here and we will be continuing to host these events in the future detailing our technical endeavors. If you enjoy writing React code and building robust, extensible UI's, we want to hear from you!

For anyone considering taking the dive and moving to React -- I highly encourage giving it a try. As mentioned before, if you feel React can help provide the structure you need to solve your application's most important problems like we did, the effort will be well worth it. We've invested significantly in our migration towards modern JavaScript and we've never looked back -- creating a developer experience that we felt was worthy of our engineering team and robust enough to provide a top notch user experience.

Happy coding folks!

Comments on Hacker News