Using React in a Production App — 1 of 2

Part 1: Decisions you’ll need to make to use React in the real world

Rohan Dang
Open House
8 min readAug 21, 2017

--

This article will share how we at Opendoor used React in a real world production app, the decisions we made, and why.

You’ve probably heard of React and have been told how awesome it is and that you should be using it. You can find many “React Starter Kits” out there that get you up and running with a basic app in React. But when you try to use it for anything production-ish, you quickly realize that to do something useful, there’s a slew of tools and packages that you’ll need.

React is different from most JS frameworks, in that it is really a library, not a framework, and as such doesn’t impose any opinions about how to use it. While this gives us a lot of freedom, it can also very quickly lead to decision fatigue as you try to expand its capabilities with third-party packages.

We’ll go over our process, decisions, and alternatives considered in landing on the packages stack that helped us build a performant, testable and scalable production app using React.

Background on our project

Opendoor is on a mission to empower everyone with the freedom to move by making the process of buying and selling homes extremely easy. Often we buy the homes directly from our customers, eliminating the hassles of showings and months of uncertainty.

We wanted to adopt React given its many benefits (performance, component-first approach, industry adoption). One of React’s strengths is that it can be adopted incrementally into an existing codebase. We decided to start our migration to React with the Input Flow, which is a set of questions that a customer must complete about their home for Opendoor to accurately value it and make an offer.

With Opendoor’s continued expansion to more cities, the Input Flow became increasingly complex with forked flows and unique questions per city. For example, basement details are an important piece of information about homes in Atlanta but not so much in Phoenix or Las Vegas.

To continue scaling Opendoor, we needed to substantially change how our Input Flow worked, so it was a good opportunity to take this isolated chunk and rewrite it in React.

Our goals

We wanted to ship this refactor quickly while laying in the foundations of a React app to easily expand its use in the codebase over time.

The high-level goals we wanted to accomplish were:

  1. Write reusable and testable components that could be composed into different configurations easily.
  2. Be performant. Reduce load times navigating in-between the flow and make the overall app snappy, using local state in a sane manner as the app grows.
  3. Scale well with complexity. Have a clean architecture for components to talk to the server and perform other consistent operations like recording analytics.

Note that for now, we were not trying to solve auth in our React app so I’ll skip over it, but that might be an important piece for you.

The chosen stack

In deciding which packages to add on top of React to achieve our goals, we had to not only consider which capabilities we need in our app, but for each of them, which specific package to go with.

For each decision, there were many alternatives available with competing philosophies and compelling arguments (see my earlier point about “decision fatigue”).

Our rubric was relatively straightforward: make sure the packages work well with each other, have an active community (that we could participate in as well), and are well-maintained and supported.

Note that this is what worked for us given our situation and requirements, but YMMV!

Finally, here’s the React stack that we settled on:

Opendoor’s React Stack

With that, let’s get into the details about each decision.

Why we decided on the above

Redux — State Management

Redux is a simple, but opinionated state management library most commonly used with React. It greatly simplifies how you think about state in your app.

Redux maintains a single store for the entire state of your app, and it is read-only. Any changes to the state are dispatched through “action” objects that carry the necessary information needed to change the state. The dispatched actions run through a reducer whose job is to use the information in the action to return a new state for your app.

This unidirectional flow of state changes has couple benefits:

  • Makes state changes extremely clear and explicit — no direct manipulation of the state, everything happens through action objects enforcing a clear protocol
  • Allows for “time travel debugging” as you step back and forth between each variation of the state since dispatched actions can be simply replayed

Goal wins: Helps build a scalable architecture, bringing sanity to state management, as well as making the app more testable and easier to debug!

Alternatives: MobX, Relay, this.setState (i.e. React’s built-in state)

Why Redux: Strong community support and popularity over competing packages. Way more scalable architecture than managing state using the built-in React APIs.

ImmutableJS — Immutable State Objects

ImmutableJS (mostly just called ‘Immutable’) is a collection of immutable data structures with a comprehensive set of available operations.

Using immutable type for your state ensures you never accidentally modify your state directly, as well as allows for efficient change detection and memoization wins.

Goal wins: Makes the app more performant through reference equality checks instead of value equality checks for detecting when to re-render!

Alternatives: seamless-immutable, sheer willpower (no immutable type, being careful to not mutate data directly)

Why ImmutableJS: Pretty good built-in support for common operations (think: built-in lodash), and strong evidence of successful use with React.

Reselect — Memoized derived data from the store

While you can access store data directly from your components, most likely in a real-world app, you’ll have some derived data that you compute from your state and want consistent access to in your components. Additionally, depending upon the complexity, you’d want it to be memoized for performance reasons.

This is where Reselect comes in. It acts like an access layer around your Redux store giving you memoized access to direct and derived data from the store. It’s really useful, and you most likely want it.

Goal wins: Big performance boost through memoization, especially when getting into complex selectors that can get expensive to re-compute!

Redux-Saga — Async Side-Effects

Sagas are basically middleware for your Redux app that can listen for certain dispatched actions and trigger any ancillary operations you want as a result of those actions being fired.

Some examples of when to use sagas:

  • Handling API requests to the server
  • Recording analytics events
  • Firing notifications on certain triggers

Goal wins: Helps in scaling architectural complexity as the interface components gets separated from any business logic, allowing the components to only worry about dispatching actions to indicate what happened and side-effects take care of updating state and performing other actions as necessary.

Alternatives: Redux-Thunk, Redux-Observable, redux-loop

Why Redux-Saga: Easier to test, consolidated place for your app’s side-effects. Also seems to be the most popular and well supported choice from a community point of view.

React Router v4 + React Router Redux — URL Routing

React Router is the de-facto choice when it comes to handling URL routing in your React app. The real question was whether to go with the older v2/v3 API or the complete re-write that is v4.

Pretty much the entire design and philosophy of React Router changed with v4, making it practically a different library from before.

React Router v4 offers some advanced techniques with routing, allowing you to make routing decisions nested deep within your app, even as the app is rendering. We’re not doing any of that for our purposes yet, but it’s something worth evaluating for your app as it can be a powerful tool.

React Router Redux is an add-on package (now folded into React Router repo), that binds the location information with your Redux store, allowing for your selectors for example to easily access the location information. This is useful if you have some information in the URL (e.g. /<entity-id>/foo) necessary for your app that you would like direct access to in your store.

Alternatives: React Router v3, redux-little-router

Why React Router v4 and react-router-redux: It’s the latest and greatest, since we were starting a new project from scratch, we wanted to adopt it now and not worry about the deprecation that’s bound to happen.

Deciding over redux-little-router was harder and in full honesty, there’s still a chance we’d evaluate switching to over, as there are some quirks and odd behaviors in react-router-redux that can be very frustrating to debug if you’re not fully aware of the intricate details of their working. We’ll go over some of the gotchas in more detail in part 2 of this post.

Jest + Enzyme — Testing Solution

Jest is a universal testing platform that works especially well as a test-runner for React with a pretty good built-in support for things like snapshot testing. When combined with Enzyme, you get access to a convenient way of traversing, manipulating and asserting the output of your React components.

Goal wins: Makes the components easy to test, allowing us to move fast with confidence!

Alternatives (to Jest): Mocha

Why Jest and Enzyme: Tight support for React in Jest, and Enzyme’s amazing support for selectors and DOM traversal to easily target elements and use simple jQuery-like syntax made it a no-brainer to go with this setup.

Helmet

A super-simple helper library to manage your <title> and <meta> tags (description, keywords, etc.) for your app. It’s the simplest (see examples), most popular library for doing this, so if you’re interested in managing and updating your document head from within the React app, you should get it.

So, how did it work out?

At Opendoor, we’ve been enjoying a successful move to React using the stack described above for couple months now. We’ve slowly validated these choices and now feel pretty confident in them.

The small chunk that we started out with has already started expanding into the rest of our codebase and is taking over the app! 🚀

Things aren’t all sunshine and rainbows though — here are a couple of tradeoffs to be aware of:

  • All these packages together introduce a lot of boilerplate code that you must write every time you want to expand functionality of the app, making the process tedious and error-prone.
  • Each package brings it’s own set of terminologies and philosophies to ramp up on, adding cognitive overhead and learning curve, resulting in a harder time onboarding new team members.
  • There’s some uncertainty in depending upon individual package owners publishing updates with backwards compatibility that doesn’t break the specific dependencies stack that you may have chosen.

That said, we think the benefits outweigh the concerns. We’re seeing a marked improvement in our app’s performance, testability and reusability wins of being component-first, and overall scalability in the architectural design.

Hope you can benefit from the same!

Next up…

In part 2, we will get into the details of how to implement this stack and any gotchas to be aware of with each package.

At Opendoor, we’re working with React and other technologies like this to build a world-class product that simplifies the process of buying and selling homes.

Come work with us! Find out more about Opendoor jobs on StackShare or on our careers site.

--

--