Simple Data Flow in React Apps Using Flux and Backbone: A Tutorial with Examples
React.js is a fantastic library. It is only one part of a front-end application stack, however. It doesn’t have much to offer when it comes to managing data and state. Facebook, the makers of React, have offered some guidance there in the form of Flux. I’ll introduce basic Flux control flow, discuss what’s missing for Stores, and how to use Backbone Models and Collections to fill the gap in a “Flux-compliant” way.
React.js is a fantastic library. It is only one part of a front-end application stack, however. It doesn’t have much to offer when it comes to managing data and state. Facebook, the makers of React, have offered some guidance there in the form of Flux. I’ll introduce basic Flux control flow, discuss what’s missing for Stores, and how to use Backbone Models and Collections to fill the gap in a “Flux-compliant” way.
Alex is an entrepreneur and Wharton grad who recently closed a startup and is contracting while he travels.
Expertise
PREVIOUSLY AT
React.js is a fantastic library. Sometimes it seems like the best thing since sliced Python. React is only one part of a front-end application stack, however. It doesn’t have much to offer when it comes to managing data and state.
Facebook, the makers of React, have offered some guidance there in the form of Flux. Flux is an “Application Architecture” (not a framework) built around one-way data flow using React Views, an Action Dispatcher, and Stores. The Flux pattern solves some major problems by embodying important principles of event control, which make React applications much easier to reason about, develop, and maintain.
Here, I’ll introduce basic Flux examples of control flow, discuss what’s missing for Stores, and how to use Backbone Models and Collections to fill the gap in a “Flux-compliant” way.
(Note: I use CoffeeScript in my examples for convenience and brevity. Non-CoffeeScript developers should be able to follow along, and can treat the examples as pseudocode.)
Introduction to Facebook’s Flux
Backbone is an excellent and well-vetted little library that includes Views, Models, Collections, and Routes. It’s a de facto standard library for structured front-end applications, and it’s been paired with React apps since the latter was introduced in 2013. Most examples of React outside of Facebook.com so far have have included mentions of Backbone being used in tandem.
Unfortunately, leaning on Backbone alone to handle the entire application flow outside of React’s Views presents unfortunate complications. When I first began working on React-Backbone application code, the “complex event chains” that I had read about didn’t take long to rear their hydra-like heads. Sending events from the UI to the Models, and then from one Model to another and then back again, makes it hard to keep track of who was changing who, in what order, and why.
This Flux tutorial will demonstrats how the Flux pattern handles these problems with impressive ease and simplicity.
An Overview
Flux’s slogan is “unidirectional data flow”. Here’s a handy diagram showing what that flow looks like:
The important bit is that stuff flows from React --> Dispatcher --> Stores --> React
.
Let’s look at what each of the main components are and how they connect:
The docs also offer this important caveat:
Flux is more of a pattern than a framework, and does not have any hard dependencies. However, we often use EventEmitter as a basis for Stores and React for our Views. The one piece of Flux not readily available elsewhere is the Dispatcher. This module is available here to complete your Flux toolbox.
So Flux has three components:
- Views (
React = require('react')
) - Dispatcher (
Dispatcher = require('flux').Dispatcher
) - Stores (
EventEmitter = require('events').EventEmitter
)- (or, as we’ll soon see,
Backbone = require('backbone')
)
- (or, as we’ll soon see,
The Views
I won’t describe React here, since so much has been written about it, other than to say that I vastly prefer it to Angular. I almost never feel confused when writing React code, unlike Angular, but of course, opinions will vary.
The Dispatcher
The Flux Dispatcher is a single place where all events that modify your Stores are handled. To use it, you have each Store register
a single callback to handle all events. Then, whenever you want to modify a Store, you dispatch
an event.
Like React, the Dispatcher strikes me as a good idea, implemented well. As an example, an app that allows the user to add items to a to-do list might include the following:
# in TodoDispatcher.coffee
Dispatcher = require("flux").Dispatcher
TodoDispatcher = new Dispatcher() # That's all it takes!.
module.exports = TodoDispatcher
# in TodoStore.coffee
TodoDispatcher = require("./TodoDispatcher")
TodoStore = {items: []}
TodoStore.dispatchCallback = (payload) ->
switch payload.actionType
when "add-item"
TodoStore.items.push payload.item
when "delete-last-item"
TodoStore.items.pop()
TodoStore.dispatchToken = TodoDispatcher.registerCallback(TodoStore.dispatchCallback)
module.exports = TodoStore
# in ItemAddComponent.coffee
TodoDispatcher = require("./TodoDispatcher")
ItemAddComponent = React.createClass
handleAddItem: ->
# note: you're NOT just pushing directly to the store!
# (the restriction of moving through the dispatcher
# makes everything much more modular and maintainable)
TodoDispatcher.dispatch
actionType: "add-item"
item: "hello world"
render: ->
React.DOM.button {
onClick: @handleAddItem
},
"Add an Item!"
This makes it really easy to answer two questions:
- Q: What are all the events that modify
MyStore
?- A: Just check the cases in the
switch
statement inMyStore.dispatchCallback
.
- A: Just check the cases in the
- Q: What are all possible sources of that event?
- A: Simply search for that
actionType
.
- A: Simply search for that
This is much easier than, for example, looking for MyModel.set
and MyModel.save
and MyCollection.add
etc, where tracking down the answers to these basic questions gets really hard really fast.
The Dispatcher also allows you to have callbacks run sequentially in a simple, synchronous fashion, using waitFor
. For example:
# in MessageStore.coffee
MyDispatcher = require("./MyDispatcher")
TodoStore = require("./TodoStore")
MessageStore = {items: []}
MessageStore.dispatchCallback = (payload) ->
switch payload.actionType
when "add-item"
# synchronous event flow!
MyDispatcher.waitFor [TodoStore.dispatchToken]
MessageStore.items.push "You added an item! It was: " + payload.item
module.exports = MessageStore
In practice, I was shocked to see how much cleaner my code was when using the Dispatcher to modify my Stores, even without using waitFor
.
The Stores
So data flows into Stores through the Dispatcher. Got it. But how does data flow from the Stores to the Views (i.e., React)? As stated in the Flux docs:
[The] view listens for events that are broadcast by the stores that it depends on.
Okay, great. Just like we registered callbacks with our Stores, we register callbacks with our Views (which are React Components). We tell React to re-render
whenever a change occurs in the Store which was passed in through its props
.
For example:
# in TodoListComponent.coffee
React = require("react")
TodoListComponent = React.createClass
componentDidMount: ->
@props.TodoStore.addEventListener "change", =>
@forceUpdate()
, @
componentWillUnmount: ->
# remove the callback
render: ->
# show the items in a list.
React.DOM.ul {}, @props.TodoStore.items.map (item) ->
React.DOM.li {}, item
Awesome!
So how do we emit that "change"
event? Well, Flux recommends using EventEmitter
. From an official example:
var MessageStore = merge(EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* @param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
get: function(id) {
return _messages[id];
},
getAll: function() {
return _messages;
},
// etc...
Gross! I have to write all that myself, every time I want a simple Store? Which I’m supposed to use every time I have a piece of information I want to display? There has to be a better way!
The Missing Piece
Backbone’s Models and Collections already have everything Flux’s EventEmitter-based Stores seem to be doing.
By telling you to use raw EventEmitter, Flux is recommending that you recreate maybe 50-75% of Backbone’s Models & Collections every time you create a Store. Using EventEmitter for your stores is like using bare Node.js for your server when well-built microframeworks like Express.js or equivalent already exist to take care of all the basics and boilerplate.
Just like Express.js is built on Node.js, Backbone’s Models and Collections are built on EventEmitter. And it has all the stuff you pretty much always need: Backbone emits change
events and has query methods, getters and setters and everything. Plus, Backbone’s Jeremy Ashkenas and his army of 230 contributors did a much better job on all of those things than I am likely to be able to do.
As an example for this Backbone tutorial, I converted the MessageStore example from above to a Backbone version.
It’s objectively less code (no need to duplicate work) and is subjectively more clear and concise (for example, this.add(message)
instead of _messages[message.id] = message
).
So let’s use Backbone for Stores!
The FluxBone Pattern: Flux Stores by Backbone
This tutorial is the basis of an approach I have proudly dubbed FluxBone, the Flux architecture using Backbone for Stores. Here is the basic pattern of a FluxBone architecture:
- Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher. Typically, this means they are singletons.
- View components never directly modify Stores (for example, no
.set()
). Instead, components dispatch Actions to the Dispatcher. - View components query Stores and bind to their events to trigger updates.
Let’s use Backbone and Flux examples to look at each piece of that in turn:
1. Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher.
# in TodoDispatcher.coffee
Dispatcher = require("flux").Dispatcher
TodoDispatcher = new Dispatcher() # That's all it takes!
module.exports = TodoDispatcher
# in stores/TodoStore.coffee
Backbone = require("backbone")
TodoDispatcher = require("../dispatcher")
TodoItem = Backbone.Model.extend({})
TodoCollection = Backbone.Collection.extend
model: TodoItem
url: "/todo"
# we register a callback with the Dispatcher on init.
initialize: ->
@dispatchToken = TodoDispatcher.register(@dispatchCallback)
dispatchCallback: (payload) =>
switch payload.actionType
# remove the Model instance from the Store.
when "todo-delete"
@remove payload.todo
when "todo-add"
@add payload.todo
when "todo-update"
# do stuff...
@add payload.todo,
merge: true
# ... etc
# the Store is an instantiated Collection; a singleton.
TodoStore = new TodoCollection()
module.exports = TodoStore
2. Components never directly modify Stores (for example, no .set()
). Instead, components dispatch Actions to the Dispatcher.
# components/TodoComponent.coffee
React = require("react")
TodoListComponent = React.createClass
handleTodoDelete: ->
# instead of removing the todo from the TodoStore directly,
# we use the Dispatcher
TodoDispatcher.dispatch
actionType: "todo-delete"
todo: @props.todoItem
# ... (see below) ...
module.exports = TodoListComponent
3. Components query Stores and bind to their events to trigger updates.
# components/TodoComponent.coffee
React = require("react")
TodoListComponent = React.createClass
handleTodoDelete: ->
# instead of removing the todo from the TodoStore directly,
# we use the dispatcher. #flux
TodoDispatcher.dispatch
actionType: "todo-delete"
todo: @props.todoItem
# ...
componentDidMount: ->
# the Component binds to the Store's events
@props.TodoStore.on "add remove reset", =>
@forceUpdate()
, @
componentWillUnmount: ->
# turn off all events and callbacks that have this context
@props.TodoStore.off null, null, this
render: ->
React.DOM.ul {},
@props.TodoStore.items.map (todoItem) ->
# TODO: TodoItemComponent, which would bind to
# `this.props.todoItem.on('change')`
TodoItemComponent {
todoItem: todoItem
}
module.exports = TodoListComponent
I have applied this Flux and Backbone approach to my own projects, and once I re-architected my React application to use this pattern, almost all the ugly bits disappeared. It was a little miraculous: one by one, the pieces of code that had me gnashing my teeth looking for a better way were replaced by sensible flow. And the smoothness with which Backbone seems to integrate in this pattern is remarkable: I don’t feel like I’m fighting Backbone, Flux, or React in order to fit them together in a single application.
Example Mixin
Writing the this.on(...)
and this.off(...)
code every time you add a FluxBone Store to a component can get a bit old.
Here’s an example React Mixin that, while extremely naive, would certainly make iterating quickly even easier:
# in FluxBoneMixin.coffee
module.exports = (propName) ->
componentDidMount: ->
@props[propName].on "all", =>
@forceUpdate()
, @
componentWillUnmount: ->
@props[propName].off "all", =>
@forceUpdate()
, @
# in HelloComponent.coffee
React = require("react")
UserStore = require("./stores/UserStore")
TodoStore = require("./stores/TodoStore")
FluxBoneMixin = require("./FluxBoneMixin")
MyComponent = React.createClass
mixins: [
FluxBoneMixin("UserStore"),
FluxBoneMixin("TodoStore"),
]
render: ->
React.DOM.div {},
"Hello, #{ @props.UserStore.get('name') },
you have #{ @props.TodoStore.length }
things to do."
React.renderComponent(
MyComponent {
UserStore: UserStore
TodoStore: TodoStore
}
, document.body.querySelector(".main")
)
Syncing with a Web API
In the original Flux diagram, you interact with the Web API through ActionCreators only, which require a response from the server before sending actions to the Dispatcher. That never sat right with me; shouldn’t the Store be the first to know about changes, before the server?
I choose to flip that part of the diagram around: the Stores interact directly with a RESTful CRUD API through Backbone’s sync()
. This is wonderfully convenient, at least if you’re working with an actual RESTful CRUD API.
Data integrity is maintained with no problem. When you .set()
a new property, the change
event triggers a React re-render, optimistically displaying the new data. When you try to .save()
it to the server, the request
event lets you know to display a loading icon. When things go through, the sync
event lets you know to remove the loading icon, or the error
event lets you know to turn things red. You can see inspiration here.
There’s also validation (and a corresponding invalid
event) for a first layer of defense, and a .fetch()
method to pull new information from the server.
For less standard tasks, interacting via ActionCreators may make more sense. I suspect Facebook doesn’t do much “mere CRUD”, in which case it’s not surprising they don’t put Stores first.
Conclusion
The Engineering teams at Facebook have done remarkable work to push the front-end web forward with React, and the introduction of Flux gives a peek into a broader architecture that truly scales: not just in terms of technology, but engineering as well. Clever and careful use of Backbone (per this tutorial’s example) can fill the gaps in Flux, making it amazingly easy for anyone from one-person indie shops to large companies to create and maintain impressive applications.
Alex Rattray
San Francisco, CA, United States
Member since September 7, 2014
About the author
Alex is an entrepreneur and Wharton grad who recently closed a startup and is contracting while he travels.
Expertise
PREVIOUSLY AT