A Journey in React with the Car Hire Frontend Squad

Skyscanner Engineering
8 min readFeb 5, 2016

--

A Journey in React with the Car Hire Frontend Squad

Posted on February 5, 2016 by Graham Martin

The airport transfers product delivered and pushed live in the UK market by Skyscanner’s car hire tribe in mid-2015 was very much built as an MVP. The frontend architecture was ‘borrowed’ heavily from Skyscanner’s car hire product, with the result that we had large chunks of unused, untested code. When the MVP showed promise, and the car hire frontend squad were given the green light to re-architect the client application to make it more stable and maintainable, we began to think about how we’d put it together.

Our main aims for rewrite were to:

  • Keep the view/presentation layer simple to make for easier unit testing. As part of this, look at creating reusable presentational components that could be shared with car hire.
  • Improve the rendering performance, as the html for the results list. Each deal (implemented as Backbone views) was scrapped and regenerated on receiving new quotes from the API, or navigating to another page. This made for a very jumpy user experience.
  • Keep in mind that the architecture and patterns used would likely be applied to the more complex car hire product in future.

We took some time to look at our requirements and see whether any frameworks would help with their delivery. While the scope of our work was a reasonable size, we weren’t rewriting the whole application. Instead we were focusing on the view layer (the V of MVC). Our logic and state were simple and there was no complex client-side navigation required. Angular, Ember and Backbone all seemed too heavyweight and opinionated for our needs. React, however, seemed to fit pretty well as the following snippets from the React homepage demonstrate:

Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it’s easy to try it out on a small feature in an existing project.

React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding.

React’s primary attraction was it’s rendering performance. When a state change occurs, React will try to re-render the entire component subtree (more on component nesting later). The natural initial reaction to this is that it would degrade performance. But in re-rendering, React constructs a new virtual DOM — an abstract non-browser-specific version of the DOM — and uses some clever diffing techniques to calculate whether an actual DOM update is necessary. The DOM is then modified with the minimum number of changes possible. This efficient rendering was extremely appealing for our results page, where only selected areas of the page content are updated on processing pricing service poll results.

So, we made the decision to give it a try.

Experimenting with React

The first thing we had to get our head around was the JSX syntax. While it is not mandatory for writing React components (simple javascript can be used), JSX is recommended. This is because of its concise XML-like structure that is more readable when defining large component trees. React components and standard HTML tags can be mixed in JSX. For example, the following snippet creates a resultsContainer fragment from a mix of lower-case HTML tags and React components (ResultCount and Pagination) that begin with an upper case letter.

resultsContainer = ( <div> <ResultCount numOfResults={dealsList.length()} /> <div id=”rows-container” className=”rows-container”> … </div> <Pagination vertical=”airport_transfer” numberOfResults={dealsList.length()} resultsPerPage={dealsList.perPage} currentPage={dealsList.page} /> </div> )

<ResultCount numOfResults={dealsList.length()} />

<div id=”rows-container” className=”rows-container”>

<Pagination vertical=”airport_transfer”

numberOfResults={dealsList.length()}

resultsPerPage={dealsList.perPage}

currentPage={dealsList.page}

The attributes that are set on the React components are known as props and pass data to the component to be used in the rendering logic. A fundamental rule in defining React components is that a component cannot mutate its props. This is to guarantee consistency in UIs. A component can define a contract on its props by defining PropTypes. These validate the props that are passed on creation of the component instance and warn on any validation failures. There are lots of different types of out of the box PropTypes, including string, bool, function, object, array and all of these can be marked as required. When your validation requirements are not covered by the core types, custom PropTypes can be wired up simply by writing a function.

The mixing of HTML and javascript in JSX takes a bit of getting used to — after all we’ve been taught to separate our presentation from our functionality for years — but the packaging of the logic along with the markup that it affects in a self-contained component actually makes a lot of sense. The components are easily understandable, the responsibilities are clear and the tracing of complex flows from javascript -> HTML and back is simple. We found that once we overcame the initial culture shock, working with JSX was very satisfying.

React components can be nested in a tree structure. We can compose complex UIs from multiple small, well-defined, reusable components using this structure. When defining a hierarchy of components we end up with components that own other components — that is components that create instance of other components in their render() method — and that are responsible for setting the props of their owned components. This one-way data flow down the component hierarchy is what triggers the re-rendering of the component tree and is key to React’s simplicity. We found that we iterated on component design, refactoring components by moving certain presentation and logic into new, small components.

State Management

State and props are closely related but are distinct concepts. Most components will be stateless and will simply render data received through props. Others will respond to user input or handle server responses — these require state. Identifying where the state should be manipulated was challenging. We found that we were dealing with state in too many places and some simple components contained overly complex logic. We needed a better way of isolating our global state and supporting the one-way data flow. What we were missing from the jigsaw was the Flux architecture.

The Flux architecture isn’t a 1960’s spy film starring Michael Caine, it’s a pattern used by React’s creator Facebook to manage data flow in its web applications. The data flow looks like this (taken from the Flux architecture docs).

User interaction with a view causes an action to be propagated to the data store(s) by a central dispatcher component and the resulting state change(s) are reflected in any affected views. This is similar to MVC but the data flow is unidirectional.

Redux and React-redux are two complementary libraries that provide the most popular concrete implementation of this pattern in the React space. Redux implements the concept of a single store which contains the whole state of your application, as well as the dispatcher functions (called reducers) which return the resultant new state. The immutability of the state is key to predictability of the application — the reducer is a pure function that must return a new state object rather than mutating the current state. React-redux provides the bindings and plumbing code required to expose the store to all components in the hierarchy and encourages you to think of components as either container or presentational components. Only container components interact with the store — invoking actions and receiving new state — and then pass the state down the hierarchy to presentational components via props. Testing of presentational components is simple as we are only concerned with the output of the render() method while state changes are easily testable as they are decoupled from React.

Unit Testing React

The common approach to unit testing React components was, until quite recently, to render the components into a DOM (using something like jsdom) and assert against them using React’s TestUtils. However, in React v0.13, Shallow rendering was introduced. This feature does not require a DOM and effectively isolates the rendering to the component under test as it renders the component only one level deep. Child components are not instantiated so test setup is simplified. The output to the shallow rendering of the components is an object that is unfortunately quite difficult to traverse. To simplify this, we used the excellent skin-deep library, which provides you with lots of useful methods for digging through the properties. You can see from the following example how clean the tests are:

describe(“render”, -> tree = null beforeEach(-> tree = shallowRenderer(<DateInput type=”pick-up” />) ) it(“should populate classname when enabled”, -> output = tree.getRenderOutput() expect(output.props.className).toEqual(“pick-up-date-input”) ) )

tree = shallowRenderer(<DateInput type=”pick-up” />)

it(“should populate classname when enabled”, ->

output = tree.getRenderOutput()

expect(output.props.className).toEqual(“pick-up-date-input”)

What have we learned?

So, we’ve learned an awful lot about React in the time we’ve been iterating on our implementation. What we’ve ended up with in our airport transfers client application is a nicely architected solution with small, composable components that have a clear responsibility. The data flow in the application is simple and state transformations are predictable and easy to test. Of course, you can achieve these results without React or Redux but these libraries force you into thinking about your application in a way that encourages this, as well as handling the plumbing and setup for you.

There is an overhead to using React. As well as the learning curve (which hopefully will be less steep for you after you’ve read this :-)), the package(s) add around 40KB to your gzipped javascript bundle size. You may have noticed that I’ve not mentioned rendering performance improvements here, and that is because our results were inconclusive. We are still experimenting with the best way to measure this accurately but rudimentary timings from switching pages have yielded insignificant differences.

So, where do we go next? Well, improving the profiling of our application is our first goal so we can accurately measure our React timings. As I mentioned at the start of this article, we are looking to redesign our car hire results page in the near future and this will effectively require a rewrite of the client application. Our test coverage is low in some key areas so we’ll compose the page of small well-defined React components that are easily testable. We’ll move all of our state to a global store and describe state transformation in actions and implement them in a reducer function, and increase our test coverage there too. We’ll A/B test the new implementation (which we didn’t do in airport transfers due to the small level of traffic the product initially received) to ensure we’ve not degraded the user experience, and we’ll iterate. Look out for a future blog post to see how we got on.

Learn with us

Sign up for our Skyscanner Engineering newsletter for more case-studies, news and job vacancies or take a look at our current job roles available across our 10 global offices.

We’re hiring!

--

--

Skyscanner Engineering

We are the engineers at Skyscanner, the company changing how the world travels. Visit skyscanner.net to see how we walk the talk!