Skip to content

Instantly share code, notes, and snippets.

@knaman2609
Forked from rattrayalex/MessageStore_FluxBone.js
Last active August 29, 2015 14:22
Show Gist options
  • Save knaman2609/2aa3ae56f35843e16642 to your computer and use it in GitHub Desktop.
Save knaman2609/2aa3ae56f35843e16642 to your computer and use it in GitHub Desktop.

Flux + Backbone = FluxBone

A practical introduction to using Backbone for Flux Stores


Flux and Backbone play wonderfully together. Or rather, part of Backbone does a great job serving as a part of Flux.

A quick overview of Backbone, and it's shortcomings

* (Actually, before that, I tried using Bacon.js in a Functional Reactive Programming pattern, which had me excited but left me frustrated).

I started my journey* by using React with Backbone's Models and Collections, without a Flux architecture.

In general, it was very nice, and the pieces fit together okay. Here's what Backbone does, and then what it did wrong:

Backbone 101

(If you're already well-versed in Backbone, you can skip this - though your corrections would be valued!)

Backbone is an excellent ~little library that includes Views, Models, Collections, and Routes. React replaces Backbone's Views, and let's save Routes for another day. Models are simple places to store data and optionally sync them with the server using a typical REST API. Collections are just places to store a group of model instances.

Both Models and Collections emit helpful events. For example, a model emits "change" when it's been modified, a collection emits "add" when a new instance has been added, and they all emit "request" when you start to push a change to the server and "sync" once it's gone through.

You get attributes with my_instance.get('attribute') and set with my_instance.set({'attribute': 'value'}). You add model instances to collection instances with: my_list.add(my_model_instance).

By including model and url attributes on a Collection, you get all the basic CRUD operations via a REST API for free, via .save(), .fetch(), and .destroy(). You can guess which does which.

... Now you know all the Backbone you need to know for FluxBone! But just to make it concrete, here's a quick example:

var Backbone = require('backbone');

var TodoItem = Backbone.Model.extends({
  // you don't actually need to put anything here.
  // It's that easy!
});

var TodoCollection = Backbone.Collection.extend({
  model: TodoItem,
  url: '/todo',
  initialize: function(){
    this.fetch(); // sends `GET /todo` and populates the models if there are any. 
  }
});

var TodoList = new TodoCollection(); // initialize() called. Let's assume no todos were returned. 

var itemOne = new TodoItem({name: 'buy milk'});
TodoList.add(itemOne); // this will send `POST /todo` with `name=buy milk`
var itemTwo = TodoList.create({name: 'take out trash'}); // same as above.

TodoList.remove(itemOne); // sends `DELETE /todo/1`

itemTwo.on('change', function(){
  console.log('itemTwo was changed!');
});
itemTwo.on('sync', function(){
  console.log('itemTwo synced to server!');
});

itemTwo.destroy(); // sends `DELETE /todo/2`. 
// > itemTwo was changed!
// > itemTwo synced to server!

Backbone's shortcomings

Unfortunately, leaning on Backbone alone to handle the entire application flow outside of React's Views wasn't quite working for me. The "complex event chains" that I had read about 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, just felt obviously wrong, especially after reading about Flux. My code, frankly, was gross. It took forever to find who was changing who, in what order, and why.

So I took another look at the mysterious "architecture-not-a-framework".

A less quick Overview of Flux, and it's missing piece

(If you're already well-versed in Flux, you can skip this - though your corrections would be valued!)

Flux's slogan is "one-way data flow" (err, they say "unidirectional"). Here's what that flow looks like:

Flux Diagram

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:

From the Flux docs:

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:

  1. Views (React = require('react'))
  2. Dispatcher (Dispatcher = require('Flux').Dispatcher)
  3. Stores (EventEmitter = require('EventEmitter'))
  • (or, as we'll soon see, Backbone = require('backbone'))

React

I won't discuss 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. I've written and erased about twenty versions of "I love React it's great software really wow" so I'll just leave it at that.

The Dispatcher

The Flux Dispatcher is a single place where all events 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. Here's a quick and dirty example:

// in MyDispatcher.js
var Dispatcher = require('flux').Dispatcher;
var MyDispatcher = new Dispatcher(); // tah-dah! Really, that's all it takes. 
module.exports = MyDispatcher;

// in MyStore.js
var MyDispatcher = require('./MyDispatcher');

MyStore = {}; 
MyStore.dispatchCallback = function(payload) {
  switch (payload.actionType) {
    case 'add-item':
      MyStore.push(payload.item);
      break;
    case 'delete-last-item':
      // we're not actually using this, 
      // but it gives you an idea of what a dispatchCallback looks like.
      MyStore.pop();
      break;
  }
}
MyStore.dispatchToken = MyDispatcher.registerCallback(MyStore.dispatchCallback);
module.exports = MyStore;

// in MyComponent.js
var MyDispatcher = require('./MyDispatcher');

MyComponent = React.createClass({
  handleAddItem: function() {
    MyDispatcher.dispatch({
      actionType: 'add-item',
      item: 'hello world'
    })
  },
  render: function() {
    return React.DOM.button(
      {onClick: this.handleAddItem}, 
      'Add an Item!'
    );
  }
});

This makes it really easy to answer two questions:

  1. Q: What are all the events that modify MyStore?
  • A: You go to MyStore.dispatchCallback, and browse through the case statements. This is surprisingly readable.
  1. Q: What are all possible sources of that event?
  • A: grep (or rather, use Sublime Text's find-across-files) for that actionType.

This is much easier than looking for, eg; MyModel.set AND MyModel.save AND MyCollection.add etc. Tracking down the answers to these basic questions got really hard really fast.

The Dispatcher also allows you to have callbacks run sequentially in a simple, synchronous fashion, using waitFor. Eg;

// in MyMessageStore.js

// this isn't actually how you're supposed to set up a store... 
// but it gives you the right idea. 
// We'll see the FluxBone way later. 
MessageStore = {items: []}; 

MessageStore.dispatchCallback = function(payload) {
  switch (payload.actionType) {
    case 'add-item': 
      
      // We only want to tell the user an item was added 
      // once it's done being added to MyStore.
      // yay synchronous event flow!
      MyDispatcher.waitFor([MyStore.dispatchToken]);  // <------ the important line!
      
      // This will be displayed by the MessageComponent in React.
      MessageStore.items.push('You added an item! It was: ' + payload.item); 
      
      // hide the message three seconds later.
      // (tbh, I'm not sure how kosher this is...)
      setTimeout(function(){
        MyDispatcher.dispatch({
          actionType: 'hide-message',
        })
      }, 3000);
      break;
    case 'hide-message': 
      // delete first item in MessageStore.
      MessageStore.items.shift(); 
      break;
  }
} 

In practice, I was shocked to see how much cleaner my code was when using this approach to modify my Stores (err, Models & Collections) compared with straight Backbone, even without using waitFor.

Stores

So data flows into Stores through the Dispatcher. Got it. But how does data flow from the Stores to the Views (React)?

[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. (Or, rather, for each Store passed in).

For example:

// in MyComponent.js

MyComponent = React.createClass({
  componentDidMount: function() {
    // register a callback on MyStore
    // to tell this component to forceUpdate
    // whenever it triggers a "change" event.
    this.props.MyStore.addChangeListener(function(){
      this.forceUpdate();
    }.bind(this));
  },
  componentWillUnmount: function() {
    // remove the callback
  },
  render: function() {
    // show the items in a list.
    return React.DOM.ul({}, 
      this.props.MyStore.items.map(function(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?? Do you think I have unlimited numbers of Facebook Engineers or something??!!11! (okay, I can think of one place that's true...)

Flux Stores: 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 "Node.js" for your server. Okay, I'll do that, but I'll do it through Express.js or equivalent: a well-built microframework that's taken 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's taken care of all the basics and boilerplate: Backbone emits "change" events and has query methods and getters and setters and everything. Plus, jashkenas and his army of 230 contributors did a much better job on all of those things than I or you ever will.

As an example, I converted the MessageStore example from above to a "FluxBone" version. (Note that it's incomplete (ie; I only converted that file) and is untested).

It's objectively less code (no need to duplicate work) and is subjectively more clear/concise (eg; this.add(message) instead of _messages[message.id] = message).

So let's use Backbone for Stores!

The FluxBone Pattern ©

After some experimentation, this pattern for using Backbone Collections and Models as Flux Stores has got me excited:

  1. Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher. Typically, this means they are singletons.
  2. Components never directly modify Stores (eg; no .set()). Instead, components dispatch Actions to the Dispatcher.
  3. Components query Stores and bind to their events to trigger updates.

Let's look at each piece of that in turn:

1. Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher.

// dispatcher.js
Dispatcher = require('Flux').Dispatcher

TodoDispatcher = new Dispatcher();  // yep, it's that easy!

module.exports = TodoDispatcher;
// stores/TodoStore.js
var Backbone = require('backbone');
var 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: function() {
    this.dispatchToken = TodoDispatcher.register(this.dispatchCallback)
  },
  dispatchCallback: function(payload) {
    switch (payload.actionType) {
      // remove the Model instance from the Store.
      case 'todo-delete':
        this.remove(payload.todo);
        break;
      case 'todo-add': 
        this.add(payload.todo);
        break;
      case 'todo-update': 
        // do stuff...
        this.add(payload.todo, {'merge': true});
        break;
      // ... etc
    }
  }.bind(this)
});

// the Store is an instantiated Collection. aka a singleton.
// (if we were to only ever have one item, 
//  it would be an instantiated Model instead).
TodoStore = new TodoCollection()

module.exports = TodoStore

2. Components never directly modify Stores (eg; no .set()). Instead, components dispatch Actions to the Dispatcher.

// actions.js
var TodoDispatcher = require('./dispatcher')

actionCreator = {
  deleteTodo: function(todoItem) {
    // dispatch 'todo-delete' action
    TodoDispatcher.dispatch({
      actionType: 'todo-delete',
      todo: todoItem
    });
  },
  // ... other actions ...
}

module.exports = actionCreator
// components/TodoComponent.js
var actionCreator = require('../actions');
var React = require('react');

TodoListComponent = React.createClass({
  // ...
  handleTodoDelete: function() {
    // instead of removing the todo from the TodoStore directly,
    // we use the dispatcher. #flux
    actionCreator.deleteTodo(this.props.todoItem);
    // ** OR: **
    TodoDispatcher.dispatch({
      actionType: 'todo-delete',
      todo: this.props.todoItem
    });
  },
  // ...
});

module.exports = TodoListComponent;

3. Components query Stores and bind to their events to trigger updates.

// components/TodoComponent.js
var React = require('react');
// ...

TodoListComponent = React.createClass({
  // ... 
  componentDidMount: function() {
    // the Component binds to the Store's events
    this.props.todoStore.on('add remove reset', function(){
      this.forceUpdate()
    }.bind(this), this);
  },
  componentWillUnmount: function() {
    // turn off all events and callbacks that have this context
    this.props.todoStore.off(null, null, this);
  },
  // ...
  render: function() {
    return React.DOM.ul({},
      this.props.todoStore.map(function(todoItem){
        // TODO: TodoItemComponent (which would bind to the )
        return TodoItemComponent({todoItem: todoItem});
      })
    )
  }
});

You can see that all put together in the example.js file in this gist.

This all fits together really smoothly, in my eyes.

In fact, once I re-architected my 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.

Note that I didn't even need to use waitFor; it may be a feature, but it's not the primary one. Just the general Flux architecture makes sense. I didn't really get how it was that different before using it. And the smoothness with which Backbone seems to integrate in this pattern is remarkable: not once did I feel like I was fighting Backbone.

Syncing with a Web API

In the original Flux diagram, you interact with the Web API through ActionCreators only. That never sat right with me; shouldn't the Store be the first to know about changes, before the server?

I 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. You can even tie into the request and sync events to easily display loading icons (kinda like this).

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 do things that way.

It may also be my youthful naivete that's causing me to interact with the web directly via Stores even for CRUD; I'm all ears to other explanations for the recommended Flux architecture, and why this might not be a good idea.

Next Steps

React and Flux have been criticized for not including Routes. I'm hopeful that Backbone's Router, perhaps coupled with a FluxBone CurrentPageStore, will provide this.

Writing the examples for this post in JavaScript was a reminder of how much I appreciate CoffeeScript. I've found Coffee and React/FluxBone get on swimmingly, and I hope to write something soon on how I pair them.

Lastly, I'd love feedback on the above! Does this seem like a good pattern to you? Are there improvements or flaws you would suggest ammending?

Flux + Backbone = FluxBone

A practical introduction to using Backbone for Flux Stores


React.js is an incredible library. Sometimes it feels like the best thing since sliced Python. When the React devs started talking about Flux, an architecture that React fits into, I was excited, but a bit befuddled. After looking through their recent docs and examples, I realized there was still something missing with Flux "Stores". FluxBone is the pattern I use that fills the gaps I found, using Backbone's Models and Collections to underlie Stores.

The result has felt as simple and pleasant to use as React.

<<< ONE-SENTENCE SUMMARY OF THE TWO AND HOW THEY PLAY TOGETHER >>>

A quick overview of Backbone, and it's shortcomings

I started my journey by using React with Backbone's Models and Collections, without a Flux architecture.

In general, it was very nice, and the pieces fit together okay. Here's what Backbone does, and then what it did wrong:

Backbone 101

(If you're already well-versed in Backbone, you can skip this - though your corrections would be valued!)

Backbone is an excellent ~little library that includes Views, Models, Collections, and Routes. Models are simple places to store data and optionally sync them with the server using a typical REST API. Collections are just places to store multiple model instances; think a SQL table. (React replaces Backbone's Views, and let's save Routes for another day.)

Both Models and Collections emit helpful events. For example, a model emits "change" when it's been modified, a collection emits "add" when a new instance has been added, and they all emit "request" when you start to push a change to the server and "sync" once it's gone through.

By including model and url attributes on a Collection, you get all the basic CRUD operations via a REST API for free, via .save(), .fetch(), and .destroy(). You can guess which does which.

... Now you know all the Backbone you need to know for FluxBone! But just to make it concrete, here's a quick example:

var Backbone = require('backbone');

var TodoItem = Backbone.Model.extend({
  // you don't actually need to put anything here.
  // It's that easy!
});

var TodoCollection = Backbone.Collection.extend({
  model: TodoItem,
  url: '/todo',
  initialize: function(){
    this.fetch(); // sends `GET /todo` and populates the models if there are any. 
  }
});

var TodoList = new TodoCollection(); // initialize() called. Let's assume no todos were returned. 

var itemOne = new TodoItem({name: 'buy milk'});
TodoList.add(itemOne); // this will send `POST /todo` with `name=buy milk`
var itemTwo = TodoList.create({name: 'take out trash'}); // shortcut for above.

TodoList.remove(itemOne); // sends `DELETE /todo/1`

itemTwo.on('change', function(){
  console.log('itemTwo was changed!');
});
itemTwo.on('sync', function(){
  console.log('itemTwo synced to server!');
});

itemTwo.destroy(); // sends `DELETE /todo/2`. 
// > itemTwo was changed!
// > itemTwo synced to server!

Backbone's shortcomings

Unfortunately, leaning on Backbone alone to handle the entire application flow outside of React's Views wasn't quite working for me. The "complex event chains" that I had read about 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, just felt obviously wrong, especially after reading about Flux. My code, frankly, was gross. It took forever to find who was changing who, in what order, and why.

So I took another look at the mysterious "architecture-not-a-framework".

An Overview of Flux, and it's missing piece

(If you're already well-versed in Flux, you can skip this - though your corrections would be valued!)

Flux's slogan is "unidirectional data flow". Here's what that flow looks like:

Flux Diagram

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:

From the Flux docs:

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:

  1. Views (React = require('react'))
  2. Dispatcher (Dispatcher = require('Flux').Dispatcher)
  3. Stores (EventEmitter = require('EventEmitter'))
  • (or, as we'll soon see, Backbone = require('backbone'))

React

I won't discuss 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. I've written and erased about twenty versions of "I love React it's great software really wow" so I'll just leave it at that.

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. Here's a quick and dirty example:

// in MyDispatcher.js
var Dispatcher = require('flux').Dispatcher;
var MyDispatcher = new Dispatcher(); // tah-dah! Really, that's all it takes. 
module.exports = MyDispatcher;

// in MyStore.js
var MyDispatcher = require('./MyDispatcher');

MyStore = []; 
MyStore.dispatchCallback = function(payload) {
  switch (payload.actionType) {
    case 'add-item':
      MyStore.push(payload.item);
      break;
    case 'delete-last-item':
      MyStore.pop();
      break;
  }
}
MyStore.dispatchToken = MyDispatcher.registerCallback(MyStore.dispatchCallback);
module.exports = MyStore;

// in MyComponent.js
var MyDispatcher = require('./MyDispatcher');

MyComponent = React.createClass({
  handleAddItem: function() {
    // note: you're NOT just pushing directly to the store!
    // (the restriction of moving through the dispatcher 
    // makes everything much more modular and maintainable)
    MyDispatcher.dispatch({
      actionType: 'add-item',
      item: 'hello world'
    })
  },
  render: function() {
    return React.DOM.button(
      {onClick: this.handleAddItem}, 
      'Add an Item!'
    );
  }
});

This makes it really easy to answer two questions:

  1. Q: What are all the events that modify MyStore?
  • A: You go to MyStore.dispatchCallback, and browse through the case statements. This is surprisingly readable.
  1. Q: What are all possible sources of that event?
  • A: grep for that actionType.

This is much easier than looking for, eg; MyModel.set AND MyModel.save AND MyCollection.add etc. Tracking down the answers to these basic questions got really hard really fast.

The Dispatcher also allows you to have callbacks run sequentially in a simple, synchronous fashion, using waitFor. Eg;

// in MyMessageStore.js

var MyDispatcher = require('./MyDispatcher');
var MyStore = require('./MyStore');

// this isn't actually how you're supposed to set up a store... 
// but it gives you the right idea. 
// We'll see the FluxBone way later. 
MessageStore = {items: []}; 

MessageStore.dispatchCallback = function(payload) {
  switch (payload.actionType) {
    case 'add-item': 
      
      // We only want to tell the user an item was added 
      // once it's done being added to MyStore.
      // yay synchronous event flow!
      MyDispatcher.waitFor([MyStore.dispatchToken]);  // <------ the important line!
      
      // This will be displayed by the MessageComponent in React.
      MessageStore.items.push('You added an item! It was: ' + payload.item); 
      
      // hide the message three seconds later.
      // (tbh, I'm not sure how kosher this is...)
      setTimeout(function(){
        MyDispatcher.dispatch({
          actionType: 'hide-message',
        })
      }, 3000);
      break;
    case 'hide-message': 
      // delete first item in MessageStore.
      MessageStore.items.shift(); 
      break;
  }
} 

In practice, I was shocked to see how much cleaner my code was when using this approach to modify my Stores (err, Models & Collections), even without using waitFor.

Stores

So data flows into Stores through the Dispatcher. Got it. But how does data flow from the Stores to the Views (React)?

[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 MyComponent.js
var React = require('react');

MyComponent = React.createClass({
  componentDidMount: function() {
    // register a callback on MyStore
    // to tell this component to forceUpdate
    // whenever it triggers a "change" event.
    this.props.MyStore.addEventListener("change", function(){
      this.forceUpdate();
    }.bind(this));
  },
  componentWillUnmount: function() {
    // remove the callback
  },
  render: function() {
    // show the items in a list.
    return React.DOM.ul({}, 
      this.props.MyStore.items.map(function(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?? Do you think I have unlimited numbers of Facebook Engineers or something? (okay, I can think of one place that's true...)

Flux Stores: 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 "Node.js" for your server. Okay, I'll do that, but I'll do it through Express.js or equivalent: a well-built microframework that's taken 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 and getters and setters and everything. Plus, jashkenas and his army of 230 contributors did a much better job on all of those things than I ever will.

As an example, I converted the MessageStore example from above to a "FluxBone" version. (Note that it's incomplete (ie; I only converted that file) and is untested).

It's objectively less code (no need to duplicate work) and is subjectively more clear/concise (eg; this.add(message) instead of _messages[message.id] = message).

So let's use Backbone for Stores!

The FluxBone Pattern ©

  1. Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher. Typically, this means they are singletons.
  2. Components never directly modify Stores (eg; no .set()). Instead, components dispatch Actions to the Dispatcher.
  3. Components query Stores and bind to their events to trigger updates.

Let's look at each piece of that in turn:

1. Stores are instantiated Backbone Models or Collections, which have registered a callback with the Dispatcher.

// dispatcher.js
Dispatcher = require('Flux').Dispatcher

TodoDispatcher = new Dispatcher();  // yep, it's that easy!

module.exports = TodoDispatcher;
// stores/TodoStore.js
var Backbone = require('backbone');
var 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: function() {
    this.dispatchToken = TodoDispatcher.register(this.dispatchCallback)
  },
  dispatchCallback: function(payload) {
    switch (payload.actionType) {
      // remove the Model instance from the Store.
      case 'todo-delete':
        this.remove(payload.todo);
        break;
      case 'todo-add': 
        this.add(payload.todo);
        break;
      case 'todo-update': 
        // do stuff...
        this.add(payload.todo, {'merge': true});
        break;
      // ... etc
    }
  }.bind(this)
});

// the Store is an instantiated Collection. aka a singleton.
// (if we were to only ever have one item, 
//  it would be an instantiated Model instead).
TodoStore = new TodoCollection()

module.exports = TodoStore

2. Components never directly modify Stores (eg; no .set()). Instead, components dispatch Actions to the Dispatcher.

// components/TodoComponent.js
var actionCreator = require('../actions');
var React = require('react');

TodoListComponent = React.createClass({
  // ...
  handleTodoDelete: function() {
    // instead of removing the todo from the TodoStore directly,
    // we use the dispatcher. #flux
    TodoDispatcher.dispatch({
      actionType: 'todo-delete',
      todo: this.props.todoItem
    });
  },
  // ...
});

module.exports = TodoListComponent;

3. Components query Stores and bind to their events to trigger updates.

// components/TodoComponent.js
var React = require('react');
// ...

TodoListComponent = React.createClass({
  // ... 
  componentDidMount: function() {
    // the Component binds to the Store's events
    this.props.todoStore.on('add remove reset', function(){
      this.forceUpdate()
    }.bind(this), this);
  },
  componentWillUnmount: function() {
    // turn off all events and callbacks that have this context
    this.props.todoStore.off(null, null, this);
  },
  // ...
  render: function() {
    return React.DOM.ul({},
      this.props.todoStore.map(function(todoItem){
        // TODO: TodoItemComponent, which would bind to 
        // `this.props.todoItem.on('change')`
        return TodoItemComponent({todoItem: todoItem});
      })
    )
  }
});

You can see that all put together in the example.js file in this gist.

This all fits together really smoothly, in my eyes.

In fact, once I re-architected my 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.

Note that I didn't even need to use waitFor; it may be a feature, but it's not the primary one. Just the general Flux architecture makes sense. I didn't really get how it was that different before using it. And the smoothness with which Backbone seems to integrate in this pattern is remarkable: not once did I feel like I was fighting Backbone, Flux, or React in order to fit them together.

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 (at least when you're spoiled enough to not have to write any EventEmitter code yourself).

Here's an example React Mixin that, while extremely naiive, would certainly make iterating quickly even easier:

// in FluxBoneMixin.js
module.exports = function(propName) {
  return {
    componentDidMount: function() {
      this.props[propName].on('all', function(){
        this.forceUpdate();
      }.bind(this), this);
    },
    componentWillUnmount: function() {
      this.props[propName].off('all', function(){
        this.forceUpdate();
      }.bind(this), this);
    }
  };
};

// in MyComponent.js
var React = require('react');
var FluxBoneMixin = require('./FluxBoneMixin');
var UserStore = require('./UserStore');
var TodoStore = require('./TodoStore');

var MyComponent = React.createClass({
  mixins: [FluxBoneMixin('UserStore'), FluxBoneMixin('TodoStore')]
  render: function(){
    return React.DOM.div({},
      'Hello, ' + this.props.UserStore.get('name') +
      ', you have ' + this.props.TodoStore.length + 
      'things to do.'
    )
  }
});

React.renderComponent(
  MyComponent({
    MyStore: MyStore, 
    TodoStore: TodoStore
  }),
  document.querySelector('body')
);

Syncing with a Web API

In the original Flux diagram, you interact with the Web API through ActionCreators only. That never sat right with me; shouldn't the Store be the first to know about changes, before the server?

I 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. You can even tie into the request and sync events to easily display loading icons (kinda like this).

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 do things the officially recommended way.

It may also be my youthful naivete that's causing me to interact with the web directly via Stores even for CRUD; I'm all ears to other explanations for the recommended Flux architecture, and why the above might not be a good idea.

Next Steps

  • React and Flux have been criticized for not including Routes. I'm hopeful that Backbone's Router, perhaps coupled with a FluxBone CurrentPageStore, will provide this.

  • Writing the examples for this post in JavaScript was a reminder of how much I appreciate CoffeeScript. I've found Coffee and React/FluxBone get on swimmingly, and I hope to write something soon on how I pair them.

  • Lastly, I'd love feedback on the above! Does this seem like a good pattern to you? Are there improvements or flaws you would suggest ammending?

myFunc = (number) ->
for i in [0..number]
setTimeout ->
console.log 'hello ' + i
, 1000
myFunc(10)

Who needs JSX when you can have Whitespace?

CoffeeScript + React = (y)

Note to people reviewing the other piece: this one isn't done yet. You're more than welcome to look it over if you like, but really don't have to!

I went back and forth a number of times in the great debate between Angular.js and React.js for client-side rendering. I ultimately landed firmly on the side of React: Angular.js, I found, was a disappointingly leaky abstraction with unfortunately high cognitive overhead. In contrast, React handles its self-appointed task with completeness, elegance, and pragmatism. I have almost never been confused when writing React code.

But there was one thing I couldn't stand about React: JSX. It was the #1 thing keeping me from using the framework. As a full-stack designer/developer, I had long since gotten fed up with editing HTML: pointless angle brackets and closing tags always felt like they were getting in the way. Jade (well, actually pyjade) became my templating language of choice, and suddenly designs become much easier to write, read, and edit.

Once you stop writing closing angle brackets, you never want to go back.

For example, take this snippet of code from the bootstrap docs:

<form role="form">
  <div class="form-group">
    <label for="exampleInputEmail1">Email address</label>
    <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
  </div>
  <div class="form-group">
    <label for="exampleInputPassword1">Password</label>
    <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
  </div>
  <div class="form-group">
    <label for="exampleInputFile">File input</label>
    <input type="file" id="exampleInputFile">
    <p class="help-block">Example block-level help text here.</p>
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> Check me out
    </label>
  </div>
  <button type="submit" class="btn btn-default">Submit</button>
</form>

In Jade, that'd be:

form(role='form')
  .form-group
    label(for='exampleInputEmail1') Email address
    input#exampleInputEmail1.form-control(
      type='email'
      placeholder='Enter email')
  .form-group
    label(for='exampleInputPassword1') Password
    input#exampleInputPassword1.form-control(
      type='password'
      placeholder='Password')
  .form-group
    label(for='exampleInputFile') File input
    input#exampleInputFile(type='file')
    p.help-block Example block-level help text here.
  .checkbox
    label
      input(type='checkbox')
      | Check me out
  button.btn.btn-default(type='submit') Submit

Ah, so much more readable! And about 2/3 the line count. Less typing, less distraction from the meat of what's going on.

So suffice it to say I wasn't looking forward to having to write JSX:

<Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;

The muddling of pseudo-html with javascript (neither of them pretty in their own right) just wasn't going to do it for me.

I searched near and far for a way to use my beloved Jade with React. It wasn't looking good. Until I stumbled accross [this post](TODO: FIND!) which demonstrated how you can use pure coffeescript to achieve almost the same effect.

Here's what that form would look like:

React = require('react')  # thanks, browserify!

{div, p, form, label, input, button} = React.DOM

MyComponent = React.createClass
  render: ->
    form {role: 'form'},
      div {className: 'form-group'},
        label {for: 'exampleInputEmail1'}, "Email address"
        input {
          id: 'exampleInputEmail1'
          className: 'form-control'
          type: 'email'
          placeholder: 'Enter email'
        }
      div {className: 'form-group'},
        label {for: 'exampleInputPassword1'}, "Password"
        input {
          id: 'exampleInputPassword1'
          className: 'form-control'
          type: 'password'
          placeholder: 'Password'
        }
      div {className: 'form-group'},
        label {for: 'exampleInputFile'}, "File input"
        input {
          id: 'exampleInputFile'
          type: 'file'
        }
        p {className: 'help-block'}, 
          "Example block-level help text here."
      div {className: 'checkbox'},
        label {},
          input {type: 'checkbox'},
            "Check me out"
      button {
        className: 'btn btn-default'
        type: 'submit'
      }, "Submit"

This is so similar to Jade that it was almost a pure copy/paste.

But it's better: it's just pure coffeescript. Where JSX involves inserting pseudo-html into javascript and then javascript into that, this is just coffeescript in coffeescript. I can have if statements, for loops, list comprehensions, and it's one seamless templating language that doesn't even require a templating language.

Eg;

# ...
render: ->
  if @props.user.isLoggedIn()
    for todo in @props.user.todos
      TodoItem {todo}
  else
    p {},
      "You should Log in! here's why:"
    ul {}, 
      [li({}, reason) for reason in @props.reasons_to_log_in]  
    

Now, there are a few things to be careful of. I sure do have fun with coffeescript's whitespace, but it can give you a sharp kick from time to time. For example, you need to remember the trailing comma after the attributes. Actually, that's the only one that I've run into.

The Tooling

I feel a little silly writing about the tooling needed for this setup. There basically isn't any: I just use browserify by way of coffeeify. This is the meat of my gulpfile:

gulp.task('scripts', function() {
  gulp.src('coffee/app.coffee')
    .pipe(coffeelint())
    .pipe(coffeelint.reporter())
    .pipe(coffeeify())
    .pipe(gulp.dest('js'))
    .pipe(refresh(livereload_server))
    ;
});
gulp.task('watch', function() {
  gulp.watch('coffee/**/*.coffee', ['scripts']);
});

You'll notice that I watch all files in my coffee dir, but only compile app.coffee; everything else is imported via require().

Having run npm install reactify --save-dev, I can also import npm modules that lean on jsx, like react-bootstrap.

<<< TALK ABOUT HOW COMPONENTS MAKE BIG TEMPLATE FILES UNNECESSARY >>>


CoffeeScript Saves Lives

Okay, that's an exaggeration. But, if you play fast and loose with philosophy, not much of one.

JavaScript and JSX waste my life away. (Melodramatic, but true). When I'm writing in them, I spend a much higher percentage of my time on keystrokes that do not add value.

When I write var myFunc = function (arg) {}; instead of myFunc = (arg) ->, I am writing 14 keystrokes that I did not need to. When I write <div></div> instead of div {},, it's an extra 4 (If that was "MyComponent" instead of "div", it'd be an extra 11!).

What's 10 keystrokes in the grand scheme of things? Aren't you going a little overboard here?

I don't think so. At least, not if you're an engineer. When I code, I code a lot. In a given week, I might spend 40 hours writing code (out of 60 hours working). Those 14 keystrokes just to define a function? That might be seven seconds.

It doesn't sound like a lot until you realize that you're defining functions (+14ks), if statements (+5ks), variables (+5ks), and inserting React components (x+1ks) all day long. All day long!

Let's say coding in CoffeeScript takes 25% fewer keystrokes than coding with JavaScript/JSX. (I'd love to run a benchmark but don't know how to do so objectively). If you spend 4 hours a day physically writing code, that's a whole hour you just gained.

If you're an engineer, you're spending a significant portion of your life coding. Why waste it on literally mindless tasks like "type out the word function and then some curly braces and then a semicolon"?

The only response I can think of to this is "it doesn't take that long to type a semicolon". Right. A piece of paper isn't that thick, but when you're Dunder Mifflin, you need a whole warehouse to deal with the total thickness of all the paper you're producing.

Of course, we also spend a lot of time reading and editing code. I personally find CoffeeScript much more readable than JavaScript+JSX. Of course, I can read JavaScript and JSX, without noticeable difficulty, but it does take longer. Since the eye has less to look at, it only spends time looking at meaningful code. With Javascript, you can't help looking at the boilerplate.

Editing takes much longer because I need to move two noncontinuous lines whenever I change the position of a code block or component (that pesky closing } or </div>).

Because of semicolons alone, you're spending an extra keystroke practically every single line.

var React = require('react');
var Backbone = require('backbone');
var Dispatcher = require('Flux').Dispatcher;
// ------------------------------------------------------------------------
// Dispatcher. Ordinarily, this would go in dispatcher.js
TodoDispatcher = new Dispatcher();
// ------------------------------------------------------------------------
// Actions. Ordinarily, this would go in actions.js or an actions/ dir.
actionCreator = {
deleteTodo: function(todoItem) {
// ... do some stuff
TodoDispatcher.dispatch({
actionType: 'todo-delete',
todo: todoItem
});
// ... do more stuff
}
}
// ------------------------------------------------------------------------
// Stores. Ordinarily, these would go under eg; stores/TodoStore.js
TodoItem = Backbone.Model.extend({});
TodoCollection = Backbone.Collection.extend({
model: TodoItem,
url: '/todo', // wow, just like that, my information saves to the server!
initialize: function() {
// Flux! register with the Dispatcher so we can handle events.
this.dispatchToken = TodoDispatcher.register(this.dispatchCallback)
},
dispatchCallback: function(payload){
switch (payload.actionType) {
case 'todo-delete':
this.remove(payload.todo)
break;
// ... lots of other `case`s (which is surprisingly readable)
}
}
});
// Voila, you have a Store!
TodoStore = new TodoCollection()
// ------------------------------------------------------------------------
// Views. This would normally go in app.js or components.js
TodoListComponent = React.createClass({
componentDidMount: function() {
// the Component binds to the Store's events
this.props.todoStore.on('add remove reset', function(){
this.forceUpdate()
}.bind(this), this);
},
componentWillUnmount: function() {
// turn off all events and callbacks that have this context
this.props.todoStore.off(null, null, this);
},
handleTodoDelete: function() {
// instead of removing the todo from the TodoStore directly,
// we use the dispatcher. #flux
actionCreator.deleteTodo(this.props.todoItem)
// ** OR: **
TodoDispatcher.dispatch({
actionType: 'todo-delete',
todo: this.props.todoItem
});
},
render: function() {
return React.DOM.ul({},
this.props.todoStore.models.map(function(todoItem){
// TODO: TodoItemComponent
return TodoItemComponent({todoItem: todoItem});
})
);
}
});
React.renderComponent(
TodoListComponent({todoStore: TodoStore}),
document.querySelector('body')
);
var ChatAppDispatcher = require('../dispatcher/ChatAppDispatcher');
var ChatConstants = require('../constants/ChatConstants');
var ChatMessageUtils = require('../utils/ChatMessageUtils');
var EventEmitter = require('events').EventEmitter;
var ThreadStore = require('../stores/ThreadStore');
var merge = require('react/lib/merge');
var ActionTypes = ChatConstants.ActionTypes;
var CHANGE_EVENT = 'change';
// FluxBone!
var Backbone = require('backbone');
// now, that wasn't so painful, was it?
var Message = Backbone.Model.extends({
// same as before
getCreatedMessageData: function(text) {
var timestamp = Date.now();
return {
id: 'm_' + timestamp,
threadID: ThreadStore.getCurrentID(),
authorName: 'Bill', // hard coded for the example
date: new Date(timestamp),
text: text,
isRead: true
};
}
});
var MessageCollection = Backbone.Collection.extends({
model: Message,
// emitChange not needed, already implemented.
// addChangeListener not needed,
// use MessageStore.on('change', callback) instead.
// (since "change" is in the Backbone library,
// you don't have to put it in a CONSTANT)
// get() not needed, already implemented as get()
// getAll() not needed, use `.models` or `.where()` or `.forEach()`
getAllForThread: function(threadId) {
// now isn't that readable! who needs a for loop?
var threadMessages = this.where({threadId: threadId});
// not really sure what this is, so just copied it in.
// only change is using the Backbone getter.
threadMessages.sort(function(a, b) {
if (a.get('date') < b.get('date')) {
return -1;
} else if (a.get('date') > b.get('date')) {
return 1;
}
return 0;
});
return threadMessages;
},
// copied verbatim.
getAllForCurrentThread: function() {
return this.getAllForThread(ThreadStore.getCurrentID());
},
addMessages: function(rawMessages) {
rawMessages.forEach(function(message) {
// don't need to check for existence first, Backbone handles that.
this.add( ChatMessageUtils.convertRawMessage(
message,
ThreadStore.getCurrentID()
) );
}.bind(this))
},
markAllInThreadRead: function(threadId) {
// wow, look! querying!
// and no need to emit a change event ourselves!
this.where( {threadId: threadId} ).forEach( function(message){
message.set( {isRead: true} );
});
},
initialize: function () {
this.dispatchToken = ChatAppDispatcher.register(this.dispatchCallback);
},
dispatchCallback: function (payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.CLICK_THREAD:
ChatAppDispatcher.waitFor([ThreadStore.dispatchToken]);
this.markAllInThreadRead( ThreadStore.getCurrentID() );
// don't need to emit a change event!
break;
case ActionTypes.CREATE_MESSAGE:
var message = Message.getCreatedMessageData(action.text);
// wow, check that syntax! so clear! so terse!
this.add(message);
// don't need to emit a change event ourselves.
break;
case ActionTypes.RECEIVE_RAW_MESSAGES:
this.addMessages(action.rawMessages);
ChatAppDispatcher.waitFor([ThreadStore.dispatchToken]);
this.markAllInThreadRead(ThreadStore.getCurrentID());
// don't need to emit a change event!
break;
default:
// do nothing
}
}.bind(this)
});
// tadah, we have a Store!
var MessageStore = new MessageCollection();
module.exports = MessageStore;
var ChatAppDispatcher = require('../dispatcher/ChatAppDispatcher');
var ChatConstants = require('../constants/ChatConstants');
var ChatMessageUtils = require('../utils/ChatMessageUtils');
var EventEmitter = require('events').EventEmitter;
var ThreadStore = require('../stores/ThreadStore');
var merge = require('react/lib/merge');
var ActionTypes = ChatConstants.ActionTypes;
var CHANGE_EVENT = 'change';
// FluxBone!
var Backbone = require('backbone');
// now, that wasn't so painful, was it?
var Message = Backbone.Model.extends({
// same as before
getCreatedMessageData: function(text) {
var timestamp = Date.now();
return {
id: 'm_' + timestamp,
threadID: ThreadStore.getCurrentID(),
authorName: 'Bill', // hard coded for the example
date: new Date(timestamp),
text: text,
isRead: true
};
}
});
var MessageCollection = Backbone.Collection.extends({
model: Message,
// emitChange not needed, already implemented.
// addChangeListener not needed,
// use MessageStore.on('change', callback) instead.
// (since "change" is in the Backbone library,
// you don't have to put it in a CONSTANT)
// get() not needed, already implemented as get()
// getAll() not needed, use `.models` or `.where()` or `.forEach()`
getAllForThread: function(threadId) {
// now isn't that readable! who needs a for loop?
var threadMessages = this.where({threadId: threadId});
// not really sure what this is, so just copied it in.
// only change is using the Backbone getter.
threadMessages.sort(function(a, b) {
if (a.get('date') < b.get('date')) {
return -1;
} else if (a.get('date') > b.get('date')) {
return 1;
}
return 0;
});
return threadMessages;
},
// copied verbatim.
getAllForCurrentThread: function() {
return this.getAllForThread(ThreadStore.getCurrentID());
},
addMessages: function(rawMessages) {
rawMessages.forEach(function(message) {
// don't need to check for existence first, Backbone handles that.
this.add( ChatMessageUtils.convertRawMessage(
message,
ThreadStore.getCurrentID()
) );
}.bind(this))
},
markAllInThreadRead: function(threadId) {
// wow, look! querying!
// and no need to emit a change event ourselves!
this.where( {threadId: threadId} ).forEach( function(message){
message.set( {isRead: true} );
});
},
initialize: function () {
this.dispatchToken = ChatAppDispatcher.register(this.dispatchCallback);
},
dispatchCallback: function (payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.CLICK_THREAD:
ChatAppDispatcher.waitFor([ThreadStore.dispatchToken]);
this.markAllInThreadRead( ThreadStore.getCurrentID() );
// don't need to emit a change event!
break;
case ActionTypes.CREATE_MESSAGE:
var message = Message.getCreatedMessageData(action.text);
// wow, check that syntax! so clear! so terse!
this.add(message);
// don't need to emit a change event ourselves.
break;
case ActionTypes.RECEIVE_RAW_MESSAGES:
this.addMessages(action.rawMessages);
ChatAppDispatcher.waitFor([ThreadStore.dispatchToken]);
this.markAllInThreadRead(ThreadStore.getCurrentID());
// don't need to emit a change event!
break;
default:
// do nothing
}
}.bind(this)
});
// tadah, we have a Store!
var MessageStore = new MessageCollection();
module.exports = MessageStore;
#!/usr/bin/env node
function myFunc(number) {
for (var i = 0; i < number; i++) {
setTimeout(function(){
console.log('hello ' + i);
}, 1000)
}
}
myFunc(10);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment