Using React in a Production App — 2 of 2

Part 2: An implementation guide to use React in a real world setup

Rohan Dang
Open House
9 min readOct 9, 2017

--

In Part 1, we talked about how at Opendoor we made some decisions in the way we’re using React, and how anyone running React in a real world production app might need to make those same decisions, and why. In this part, we’ll walk through how to actually implement those decisions.

First, let’s recap the React stack that we settled on:

Opendoor’s React Stack

Now, let’s talk about how you can set up all these pieces for your app.

We’ll discuss the following areas:

  1. Setting up the Redux store to manage your app’s state
  2. Setting up Redux-Saga to handle API requests and other side-effects
  3. Setting up React Router v4 to manage URL routing
  4. Finally, putting it all together

1. Manage your app’s state

Your app’s state will be held and managed by Redux, using ImmutableJS to create immutable state, and using Reselect to pull data out of it (direct and computed).

Redux is comprised of Actions and Reducers. Actions are just plain ‘ol JS objects that carry the information necessary to reduce your app state to something new. Typically, an action object has an identifier for the type of action it is, and an optional payload to go along with it. Your app invokes an action by calling a dispatch function with the action object. Redux then calls your reducer with the action object that you use to grab the type and payload out of and return a new resulting state accordingly.

If you’re interested in learning more about how Redux works, I highly recommend browsing the very readable, official Redux docs

We use ImmutableJS to create immutable state that prevents accidental modifications of the state directly. All changes to the state flow through the action → reducer path.

Reselect is used to expose parts of your state directly, as well as supplement it with computed properties that look just like a slice of the state to your app. Reselect automatically takes care of memoizing them for you!

Installation

Let’s start by installing Redux, ImmutableJS, and Reselect to create a store that you can read and write to.

npm install --save redux react-redux
npm install --save-dev redux-devtools
npm install --save immutable
npm install --save reselect

Action Types

First off, action objects need unique identifiers to represent their type. To avoid typos and accidental re-use, it’s best to use constants for this. For convenience, put them all in an actionTypes.js file, then you can easily access them from both reducers and sagas:

Action Creators

Next, you want to create some action creators, which are basically functions that take in the information you want to capture in your dispatched action, and generate the action object for you.

To be consistent, we use the FSA (Flux Standard Action) format to create action objects, which basically means the bulk of your information will be put inside a payload key in the action object. Here’s an example actions.js file:

Reducer

Now, let’s write a reducer that will be responsible for producing a new state based on the actions it receives.

Reducers are “pure functions” — given a fixed set of inputs, they always return the same state.

For this reason, you don’t put any network calls in your reducers. That’s where a middleware like saga comes in to process your network calls and emit new actions for the reducer to process.

The reducer is where you will define what your initial state should be. You’ll notice we’re using some Immutable stuff here, that’s because your reducer should be responsible for making sure only Immutable objects are going into your store and should be converting any incoming payload into Immutable objects for the new state.

ImmutableJS has pretty good documentation on the various methods you can invoke on Immutable objects to perform almost any operation you’d want. There are many guides and cheatsheets out there on ImmutableJS, but I usually found myself referencing the official docs most often.

Most important thing to remember with Immutable is when you’re dealing with immutable objects and when you’re not. It’s best to be consistent in your approach — e.g. action payloads are always JS objects, selectors are always immutable objects, etc.

The performance characteristics of converting back/forth between JS↔Immutable: fromJS (converting JS objects to Immutable) is not too bad, but toJS (converting an Immutable object back to JS) is an expensive call that you want to reasonably minimize the use of.

Now, for writing your reducer code, a common practice is in your reducer.js switch on the action type to catch the right action and return a new immutable state appropriately:

Selectors

Selectors give access to the state, allowing you to combine parts of your state to create computed properties. For example, you can have a selector that uses an ID or token in your URL (populated into the store by react-router-redux) to grab a record from another part of your store using that identifier!

Using Reselect to create your selectors, we get performance benefits through memoization for complex selectors (when the inputs don’t change). Since the state is of type Immutable, we use methods from the Immutable API to run any operations as necessary. This is a good page to bookmark for reference.

Add a selectors.js file that will hold your static and computed selector functions:

Exporting it all

At this point, you should have a directory structure like this:

/myApp
└─── actions.js
└─── actionTypes.js
└─── reducer.js
└─── selectors.js

Let’s add an index.js file here that will export all your Redux items:

And a parent reducers.js that exports all your reducers for the initializing module:

We’ll go over more code organization best practices later, but for now it’s a good idea to put all your redux code in its own directory, maybe something like store.

Your directory structure finally should look something like this:

/store
/myApp
└─── actions.js
└─── actionTypes.js
└─── index.js
└─── reducer.js
└─── selectors.js
reducers.js

Connecting your components

In order to gain access to the state, you connect your components to the state using the connect method from redux to wrap around your final component class when exporting it.

The connect function takes two arguments, first one maps parts of the state and makes them available as props within your component, and the second (optional) one maps actions as dispatch functions and makes them available on props as well. You can choose to only implement one or both, depending upon your component’s needs. The result of this call is another function that you pass your component to and export the result.

Here’s what a sample connected component might look like:

2. Handling API requests and other side-effects

In this section we’ll talk about setting up Redux-Saga to achieve asynchronous side-effects as a result of dispatching your actions.

Installing

npm install --save react-saga

Using for API calls

One of the typical use cases for this is to handle your API calls for posting and fetching data from your backend.

Sagas are invoked after Redux is done taking the action through the reducers. They can also dispatch other actions to change your application state as a result of a network call for example.

So you can imagine having a series of action and effects like this:

  1. Dispatch fetchConfig() action
    Reducer → Sets state.loading = true
    Saga → Makes an API call to fetch configuration from server
  2. On API call return, the saga dispatches receiveConfig(config)
    Reducer → Sets state.config = config; loading = false

Here’s a sample code for something like that:

In your actions.js

In your actionTypes.js

export const FETCH_CONFIG = '@@co/store/product/fetchConfig';
export const RECEIVE_CONFIG = '@@co/store/product/receiveConfig';

In your reducers.js

import { fromJS } from 'immutable';
import * as actionTypes from './actionTypes';

Finally, in your saga.js

There are two ways to resolve concurrency issues when listening for actions: takeLatest (ignore concurrent calls and take the latest) and takeEvery (each concurrent call triggers the saga). As you can imagine, both are suitable for their own unique situations.

Note, another good use case for sagas is to use it for logging analytics. Your components can simply call a trackEvent type of an action that you can have an analytics saga listen for and standardize all your tracking in one place!

3. Managing URL routing

React Router + React Router Redux — URL Routing

Installation

Let’s start by installing the libraries

npm install --save react-router-dom
npm install --save react-router-redux@next
npm install --save history

Note the react-router-redux@next above, in order access your location details through Redux, you need react-router-redux, but to make it work with the latest version of React Router (i.e. v4), you need to install the @next version. This is until a stable version is released.

Accessing from the store

React-router-redux makes the current location information available in the redux store under the location path, so you can interact with it like any other part of the redux store.

The shape of the location piece in the store looks like this:

location:
pathname
: "/current/url/path"
search: "(query params)"
hash: "(hash target)"
key: "abcd1234"

You can also modify it through these store actions that you can dispatch:
push(location), replace(location), go(number), goBack(), goForward() — like any other actions, these can be mapped directly from your components, or more appropriately, accessed via your sagas.

You can read more about it over here.

Setting up the routes

In the root of your react app, create a routes directory with index.jsx file holding your routes information, something like this:

Note: Use :name to pass portions of the URL down to the component as props.

A common scenario is to have a base Layout component wrapping most of your pages. One place you might think of doing that might be in the routes itself, by simply wrapping your <Route> elements with a <Layout> component.

An important gotcha is that any intermediate elements between your <ConnectedRouter> and <Route> would need to either be wrapped with withRouter or pass all the props down so that location changes can propagate and force children to re-render. Otherwise, you can get into some nasty bugs with your URLs changing but content not updating accordingly!

4. Putting it all together

In your base template, make sure there’s a mounting point for your React app:

// app.html<div id='root' />

Install Helmet if you’re interested in managing title/meta tags from within your React app:

npm install --save react-helmet

And finally set up your module.jsx file to look something like this:

Code Organization

As a bonus, here’s how we’ve organized our code given all these different pieces.

There are two main ways to think about organizing your React code — by domain (i.e. entity types, like ‘Button’, ‘PhotoUpload’, etc.) or by nature (i.e. types of files, like components, routes, etc.).

The approach that worked best for us was to put all our redux code (actions, reducers, selectors) under a store folder, with each redux “app” as a sub-folder, and organize components into two categories:

  • Presentational components that go under a components folder — these components are only concerned with display logic, with data and callback handlers passed down as props
  • Connected “container” components that go under a containers folder — these components are the ones that are connected to redux, do the data fetching and handling, and are responsible for composing and returning presentational components

(This approach is inspired by Dan Abramov’s article about the same.)

Here’s what such a structure would look like:

/react-app└──/components
└──/__tests__
└──FooComponent.test.jsx
└──FooComponent.jsx
└──FooComponent.css
└──/containers
└──/__tests__
└──FooPage.test.jsx
└──FooPage.jsx
└──FooPage.css
└──/hocs
└──xyzWrapper.jsx
└──/routes
└──index.jsx
└──/store
└──/myApp
└──actions.js
└──actionTypes.js
└──index.js
└──reducer.js
└──selectors.js
└──reducers.js
└──module.jsx

A note about testing

We didn’t cover how we’re using Jest and Enzyme for our tests, but I’ll leave that for another post. In general, both of those together make writing tests and testing React components a breeze, and I highly recommend it!

What’s next?

We’re in the process of moving to TypeScript to add type checking. Be sure we’ll share some of our thoughts on the migration and how well it works out for us.

Also on our list of things to investigate/incorporate: Record Immutable types for clearer representation of our data types.

We’re working hard on open-sourcing some of the tools and patterns that have been working out really well for us. Subscribe to our blog and look out for any announcements about that in the future!

Opendoor is looking for engineers of all backgrounds to build products and technologies that empower everyone with the freedom to move.

Find out more about our jobs on StackShare or on our careers site.

--

--