In the previous post, we detailed the reasoning that led to us moving away from Backbone. In this installment, we'll highlight how we framed the search that led us to React and subsequently dive into a React tutorial of how we write React components at Gusto.

Optimizing for your own use case

When evaluating upgrade options, you should think carefully about what your application's most important use cases are and how you can choose a technology that optimizes for them. For us, we optimized for 3 things:

Incremental Upgrade Path

We had built out a large, monolithic frontend application and we had to be pragmatic in how we approached the upgrade path. An entire rewrite of our frontend application was not an option. We needed a framework without a high entry cost that would allow us to migrate our application incrementally. This would enable us to continue to build a steady stream of features for our customers during the transition from Backbone to React.

Reuse of Common Components

Having already gone through a rebrand and a layout update in the past year alone, we understand the value of consolidating markup and reusing common components across an application. By having a single source of truth for all UI building blocks in the application, design updates become much easier to roll out and we can feel confident about the effect across the application.

Ease of Creating Interactive Forms

As a payroll, benefits, and HR company, we have more data entry and interactive forms than most other web applications. Whether it's onboarding an employee, running payroll, or electing new health insurance plans, we are more often collecting information from our users rather than providing views to consume data. As a result, we opted for a framework that optimizes for the view layer and allows us to write reactive forms declaratively.

Enter React.js

Picking reliable and battle-tested technologies is critical when dealing with people's payroll and benefits, and React first caught our eye with its heavy usage at Facebook and Instagram.

Encouraged by the success stories of React's usage in production, we built out a proof of concept in our application and fell in love almost immediately. These are a few of our favorite principles behind the React paradigm, and we will be going in depth with examples of how we use them in our tutorial.

Declarative UI

React has a data binding system that consists of internal state and props. state can be updated in response to user events with a call to setState, while props are arguments passed to a component to allow for easy reuse. Because state and prop changes trigger immediate re-renderings of the component, we get native data binding that allows us to write components that are declarative in nature and will automatically update based on changes to these properties. That means no more manual DOM updates and complicated if/else logic to determine what to change, but rather a clear definition of how our component will look based on a given state.

Optimized DOM Updates

React's shadow DOM was a huge development for the JS community, proven by its widespread adoption by frameworks like Ember and Angular. React has an internal representation of what is on the DOM and whenever changes are made to the data model, a new DOM will be recomputed in memory (fast) and will do the minimum possible operations to update the DOM to the new state.

Encapsulated/Composable

React ships with a PropType validation system that not only documents the arguments that your components take, but also throws warnings when components are used improperly. This allows us to write components that are reusable and is especially useful for a set of components that we call "elements", which constitute the building blocks of our UI. These include things like buttons, forms, inputs, modals, and other reusable parts of our system. Combined with React's JSX templating language for composing these components, building common UI paradigms becomes as simple as writing markup, removing the need to manually wire up components through event listeners.

Testable

The best React components are pure functions that will always render the same output based on a set of inputs. Because of their functional nature, we can rest assured that something outside of the component will never reach in and mutate the component unexpectedly, and therefore can write declarative, readable tests for how our UI should behave based on a certain set of properties.

Interfaces well with our Design Team

We have a rockstar design team here at Gusto, and it has been a pleasure to work with them on our growing UI. They own our elements and make our jobs as engineers easy by providing all the basic building blocks we need to build features without the need to write custom markup. By being able to reach for reusable components that are flexible and styled, we are able to quickly build experiences that are on brand, functional, and beautiful.

Immediate Productivity Gains

Because React is only the view layer of your application, it leaves the routing and data management up to your discretion. We had already built out a robust Backbone-based data model that was well integrated with our RESTful JSON APIs, and we hoped to reuse this with a new system. We were able to accomplish this by abstracting our Backbone models behind Flux stores, and making our components agnostic of the underlying data layer -- providing us with an easily replaceable layer to interface with Backbone.

Integrating React into our Application

Not only does React allow for our Backbone data model to be easily consumed, but it can be rendered by Backbone views and routers. This was pivotal in allowing us to begin writing React immediately and convert Backbone views as needed rather than all at once. We are still using Backbone routers to handle our client side URLs and we have simply extended them to handle React components as well.

class BenefitsRouter extends Backbone.Router
  initialize: (options) ->
    @company = options.company

  routes:
    'benefits': 'benefits'
  ...

  benefits: (highlight) ->
    view = React.createElement(BenefitsPage)
    AppView.showView(view, 'company-benefits')

Our AppView is our application container that handles displaying whatever view is in focus. We use the showView function to animate between different pages and we have modified it to mount both Backbone and React views accordingly.

Although we try to write full views in React, occasionally it is necessary to render React from Backbone views.

class AddressView extends Backbone.View
  template: HandlebarsTemplates['addresses/address']
  className: 'address-address-view'
  mixins: [ReactSubComponent]

  render: ->
    @unmountSubComponents()
    @$el.html(@template(@context()))
    @renderAddressExtensionView()
    return @
  ...

  renderAddressExtensionView: ->
    view = React.createElement(AddressExtension, { addressId: @model.id })
    @renderSubComponent(view, @$('.address-extension-view').get(0))

We are provided the renderSubComponent method from a small mixin we wrote that hooks into Coccyx so that React components are properly torn down with the view. Notice here how we don't pass the Backbone model into the component, but rather just an identifier so the component can retrieve the data itself from the store. This will guarantee that if we need to remove Backbone in the future, our React components will be using a store interface that we can refactor behind.

Helping Hands

While React itself is an incredible library for building UI, we couldn't have gotten the massive productivity boosts in our frontend systems without some complementary tools.

Immutable.js

Immutable.js is another great library released by Facebook, and allows us to be confident that the objects we operate on are not mutated unexpectedly. For example, if you were to update the state object in a React component without using the setState method, the React lifecycle would be broken as it would not be able to hook into the changes. Sometimes, when mutable objects are shared, changes can happen in far off regions of the application that are hard to track down. After being bit by this issue one too many times, we decided to adopt Immutable for all of our complex objects used in our components.

Immutable is a functional library that provides powerful methods for updating nested objects and always returns a new copy of the updated object. The guaranteed immutability allows to more simply reason about our data used by our components. This also allows to confidently use the PureRenderMixin, because even though this optimization only looks at shallow equality to determine if it needs to update a portion of the tree, the presence of a different immutable object would guarantee a change has occurred.

Enzyme

Enzyme is a testing library that was developed by AirBnB to make testing React components intuitive. Coming from a Backbone background, where jQuery is often the library used for asserting the DOM was constructed properly, the React test utils feel clunky and confusing by comparison. Enzyme provides an API that is familiar to us (a flexible find method that can assert classes, tags, and React components alike) and also gives us easy options for shallow, full, and static rendering.

Flux, Babel, ESLint, Webpack

With the use of these tools for data management, higher level language support, static analysis, and highly optimized bundling, we have been able to make the most of everything React has to offer and provide a developer experience on the frontend we previously thought to be impossible. We will take a deep dive later into our tooling and how it has taken our development experience to the next level.

We’re thrilled with our new setup and hope this article demonstrates a few of the strengths of React and the pain points that it solves for us.

Now that we’ve covered the reasons for moving off of Backbone and onto React, we’d like to demonstrate actually building a React application. The next post in this series, titled Evolving JavaScript Part 3: Building a React Application, does just that.

Comments on Hacker News