Integrating React and Redux Into an Existing Backbone App

The tools and techniques we used to add React+Redux features into a Backbone app

Intro

I've mentioned a few times that my project team works on an app that uses ES5 and Backbone, but that we built our latest couple features using React + Redux. I was asked to give more info on how we did that, and wanted to write it up as a post for easier reading.

I can only give limited details on the actual app itself, as it's a proprietary internal application. It's primarily a geovisualization app that uses the Cesium 3D globe library, with some additional data sharing capabilities. However, I can give a bit more background on the technical side of things.

App Background

The aplication started off as a rush development job, and unfortunately the initial dev phase tried to use jQuery to solve every problem. Fortunately, I was able to introduce Backbone soon after, and while there's still a couple jQuery-centric bits of code left, it's now almost entirely Backbone.

We use a mixture of Marionette (lifecycle methods and collection views), Epoxy (data binding), and Ampersand-State (a much improved Backbone.Model), plus a variety of Backbone plugins for things like declaring subview locations in templates. We use AMD modules loaded with Require.js, and bundle it using Require's r.js tool. We're also still "vendoring" libraries by downloading minified and unminified libraries and committing them to our repo. Finally, the code up until now has all been plain ES5, as we don't have a compilation step and our target environment hasn't supported ES6.

The upside to this setup is that there's no build step required whatsoever for development - it's just edit, upload, and refresh the page (the bundling step only applies to production builds). The downside is that we've been unable to use any new JS syntax, and of course can't use anything like hot reloading.

The Tipping Point

I've spent my last two years learning and evangelizing React and Redux, primarily in my free time. I also spent a large portion of 2016 prototyping a React+Redux rewrite of an existing GWT app I'd built, and trained a couple other devs on my team how to use React+Redux. But, we've had no plans to rewrite our main app, as we simply don't have time or reason to do so.

When we started our latest dev phase, I was trying to help some of the junior devs get up to speed on Backbone and how we used it. The tipping point came when I was trying to explain to one of them all the different View class types he'd need to use to implement a master/detail-type UI feature: "Well, you'll need an EpoxyItemView there, and a ScrollingSortedCollectionView here, and you'll need to create a 'viewModel" instance and pass it around, and trigger events on THIS event bus for these cases and THAT event bus for those cases, and... boy, this would be SOOO much easier if we just did it in React and Redux. Hey, waitaminute...". Fortunately, my manager was already on board with React+Redux as the way to go in the future, and agreed we could add them into our Backbone app, especially after I threw together a quick proof-of-concept showing a React component rendering inside one of our Backbone-based UI sections.

Integration Steps

There's been many articles that describe how various companies migrated apps from React to Backbone. This one will be just a bit different, in that we only added React+Redux into our existing Backbone app. Here's some of the steps and techniques that we used. I don't think any of this is particularly new or unique, but the info may be helpful if you're trying to do something similar yourself.

Rendering React Components in Backbone Views

While the typical React SPA has a single use of ReactDOM.render(), it's entirely fine to have multiple React render trees throughout a larger non-React page. In our case, we wanted to use our existing Backbone UI infrastructure for creating modal dialogs and menus, but render React components inside them for the content.

The React docs recently added a section on integrating React with other libraries, which demonstrates the basic concepts for embedding React in Backbone Views. It's just a matter of calling ReactDOM.render() somewhere during the Backbone view creation process, and ReactDOM.unmountComponentAtNode() when the view is destroyed.

There's a wide variety of pre-existing utilities and libraries to help integrate Backbone and React, or even Backbone and Redux, and I'd bookmarked many of them over time just in case they might be useful (see the Further Information section for links). We ultimately didn't use any of them as-is, but I did make use of the viewFromComponent() function from backbone.react-bridge with some changes for our needs. It takes a React component class as an argument and returns a Backbone View that creates a React render tree for that component, and also extracts specific model/collection values and passes them through as props. (I guess that makes it a "Higher-Order View"? :) ) That allowed us to build our React components in a pretty normal way, and then just wrap up the top-level components as needed so they could be attached to our Backbone UI.

Syntax and Rendering

Because we don't have a build step with compilation, we've thus far been limited to strictly ES5 syntax. During this last dev cycle, our target browser environment did get updated to versions that have a high percentage of ES6 support, so we finally began making use of ES6 syntax as we wrote new code or updated existing logic. (Hallelujah - arrow functions and destructuring FTW!)

However, we still don't have the ability to use JSX in our render methods. Everyone would agree that raw React.createElement() calls are ugly, even if you alias createElement() to h() or something. Fortunately, there's a variety of abstraction utilities for createElement() out there for those who either dislike JSX, or (like us) can't use it. I opted to use the react-hyperscript-helpers lib, which looks like this:

import {div, h} from "react-hyperscript-helpers";

const MyComponent = (props) => (
    div(".some-class", [
        h(OtherComponent, {
            a : 123,
            b : "some text",
            c : props.someValue
        })
    ])
);

It's obviously not as nice as JSX, but it's a very workable fallback given our constraints.

We also don't get to take advantage of the Class Properties syntax, so there's no arrow functions for handling class method binding. To simplify binding, we've used the react-autobind utility, which binds all instance class methods with one function call in a constructor. It's a decent tradeoff between explicitness and abstraction.

Connecting Backbone Views to Redux

As we built out our new features, we had a few places where our existing Backbone Views needed to use data that was only in the Redux store. We didn't do anything particularly fancy here - we just imported the Redux store directly into those modules, and add store.subscribe() / unsubscribe() calls in the lifecycle methods of our views. We did use the redux-watch library to simplify the process of listening for changes in specific bits of store state, but other than that, we used manual subscription handling and some extra data diffing in our views.

Final Thoughts

I loved Backbone when I first started using it, and it was indeed a huge improvement over jQuery-style spaghetti (both in general, and in our specific case). There's still some good things to say about it: the overall simplicity of its concepts and implementation, how its synchronous event bus lets you step through both your code and Backbone's internals, and the ability to use plugins for different use cases. But, even with extensions like Marionette and Ampersand adding more capabilities, it's clear that React is just a fundamentally better approach, period (especially when it comes to dealing with child components), and that a Flux-style architecture beats change events and random models being updated anywhere.

We still aren't planning to rewrite the rest of our existing app, so I'll be dealing with Backbone code for a while. But, it's safe to say that any new code will be written in React+Redux, and we'll probably rewrite pieces of functionality in React whenever we have the opportunity. Happily, we can do that incrementally as we go, and React+Redux can coexist with Backbone without too much difficulty.

Further Information


Author Avatar

Mark Erikson

Collector of interesting links, answerer of questions