Flux solutions compared by example
Nowadays Flux (together with React.js) is one of the hottest topics in the tech world.
When people talk about Flux they mean more of a pattern than a framework. But Javascript community has already made a solid input into it and developed different approaches for working with Flux in order to bring some structure and “frameworky” feel. The first thing which comes to mind after some exploration - there are too much Flux libraries right now. In spite of that let’s try to compare some of them.
As for me I prefer staying on the shoulders of giants and Facebook’s open-source Dispatcher did pretty well for me so far both in commercial and pet projects. However curiousity forced me to implement a small “proof of concept” app with different Flux libraries on board to have ability to compare and see them in action.
Unidirectional data flow
"Flux" is just a word that describes itself as an architecture with "one way" data flow. What does it really mean? There are tons of different diagrams trying to visualize the unidirectional flow of data but the one that really makes sense for me is very simple:
Actions -> Data Stores -> Components (Views)
Every data mutation inside application occurs with calling the Actions - these could be user interaction inside component or server response. Data stores listen to the actions and mutate the data within themselves. It’s important to remember that stores are not models but they contain models. On the other hand Views listen to the stores in order to get the data to display and have no manipulations with it on their own. Every component re-renders itself completely when the data change is fired. Thanks to React and Virtual DOM every render is very cheap in performance costs. React will update the real DOM with only the difference. When another user interaction is happening we call actions and the flow is repeated.
Another good self-descriptive diagram about relations of different Flux parts could look as follows:
In the draft version of this article I wanted to focus only on the trio of the most popular Flux solutions so far - authentic Flux Dispatcher by Facebook, pluggable isomorphic container known as Fluxible by Yahoo and even more functional approach and rethink of original ideas by Mikael Brassman with his Reflux implementation. However attention that I received to my comparison repository on GitHub and several pull-requests with another solutions changed my mind a bit.
So I assume that readers of this blog post are aware of basic Flux architecture principles. Facebook’s Flux is the place where to start if you don’t feel confident with them. It’s important to get original Flux ideas in your head before proceeding with some specific implementations. I hope that comparison demo will help you to choose the right library to simplify your Flux way and it will definetly expand with more useful solutions.
I strongly encourage you to fork the Flux Comparison Demo and make pull requests with some Flux implementations that could be miserebly ommited by me.
Demo
The idea behind example client-side application is quite simple, it’s a parody on numerous online shops and consists of 2 components - the list of available products and cart where to checkout them.
Choose some products from our "Flux Online Shop" and add them to cart. Open browser console and click Checkout button, you’ll see payload with products that you just “bought”.
Dispatcher and Constants
Almost all libraries use Facebook’s Dispatcher under the hood. Except Fluxible which has it’s own Dispatchr as separate module and Reflux that decided to move the dispatcher into the actions themselves and remove the singleton implementation.
// McFly is an almost classy dispatcher itself
var mcFly = new McFly();
return mcFly.dispatcher;
Also we can differentiate libraries on those who use Constants like in classic Facebook Flux (e.g. McFly, Marty) and big switch
statements in stores (I’m looking at you McFly!).
Other ones prefer to not use such concepts. That’s good ‘cause when there is no giant switch
statements in your store and the burden of constants is removed from the developer, custom dispatcher logic becomes unnecessary.
However you shouldn’t be under delusion. For example in Alt there is still a single dispatcher through which actions flow to the stores. That means that you still get the benefit of being able to hook into the dispatcher to listen to all global events. This is very useful for debugging.
// Alt.js provides access to the dispatcher
var Alt = require('alt');
var alt = new Alt();
alt.dispatcher.register(console.log);
Facebook’s dispatcher is just a part of the library that saves you from writing custom boilerplate code.
Actions
Creating and using actions in almost all libraries feel the same. Especially in those that are good in incapsulating boilerplate code.
I believe that the only right place to initiate fetching data in Flux is Action Creators. It has a lot of benefits and works as easy as firing actions when request starts and when it finishes. One of the reasons to mention why you should follow this pattern is that stores pulling in data will screw you as soon as more than one store needs to know about a data request. Hopefully all libraries in the comparison are fine with using actions as the place to make web API calls.
// ES6 flavored Alt.js's actions
class ActionsCreators {
constructor() {
// boilerplate actions
this.generateActions(
'receiveProducts',
'addToCart',
'finishCheckout'
);
}
cartCheckout(products) {
this.dispatch(products);
// make request to third-party API
WebAPIUtils.checkoutProducts(products);
}
}
alt.createActions(ActionsCreators, exports);
// Web API utils call actions on success
getAllProducts() {
shop.getProducts(
(products) => ActionCreators.receiveProducts(products);
);
},
checkoutProducts() {
shop.buyProducts(
(products) => ActionCreators.finishCheckout(products);
);
}
Very interesting discussion on where to fetch data and async requests was on the recent ReactConf 2015. Here is video that I highly recommend to watch. There are couple of useful advices as well.
Nevertheless there might be some difference in syntax. And the most wide-spread notice by the community is that calling actions in Fluxible are kind of verbose.
// Fluxible way of creating...
module.exports = function (context, payload, done) {
context.dispatch('ADD_TO_CART', {product: payload.product});
done();
};
//...and executing actions
var addToCart = require('../actions/addToCart');
this.props.context.executeAction(addToCart, {
products: this.state.products
});
Stores
In my opionion stores should be as simple as possible, prefferebly immutable, with no data fetching and async functions. Just a thin layer which describes the current state of the app. It’s better to have standalone stores that do not have dependencies on other ones. However practically sometimes (in simple cases) it’s handy to have waitFor
method:
// fragment from Marty.js' CartStore
onAddToCart: function (product) {
// waiting to product store finish data manipulations
this.waitFor(ProductStore);
var id = product.id;
product.quantity = id in this.state ? this.state[id].quantity + 1 : 1;
this.state[id] = assign({}, product[id], product);
this.hasChanged();
}
Reflux is a bit different in the terms of waiting. You can aggregate stores by listening to another data store’s change event. For me possibility of stores to listen to each other sounds a bit dangerous as your application can quickly become crumbled and hard to maintain.
// CartStore initialization in Reflux
var CartStore = Reflux.createStore({
init: function () {
this._products = {};
// listen to actions
this.listenTo(ActionCreators.cartCheckout, this.onCartCheckout);
this.listenTo(ActionCreators.finishCheckout, this.onSuccessCheckout);
// subscribe to listen for whole ProductStore as there is no `waitFor`
this.listenTo(ProductStore, noop);
this.listenTo(ActionCreators.addToCart, this.onAddToCart);
},
...
Isomorphism
I really haven’t seen a big problem with singleton instances of stores and actions if we are dealing with pure client-side app so far. This could even work on the server in some cases too. However approach that is declared by Flummox makes me think in a bit different way, especially if we’re talking about isomophic apps. Flummox does not rely on on singleton objects, spread out across multiple modules and each of the different classes can be instantiated independently from the others. This makes it very easy to test. And even more! That’s why you get isomorphism for free - just create a new Flux()
instance on each request. Flummox lacks some API docs for a moment but this should be improved very soon.
One of the coolest Alt.js features is Snapshots. The concept which borrows some from Fluxible’s dehydration/rehydration. At any point in time you can takeSnapshot()
of your application and have entire state serialized for persistence, transfering, logging, or debugging. It’s easy to bootstrap serialized data and get your application in the particular state.
Components vs. Containers
What I really like in Flux is that our components are not aware of any code changes in the flow. That’s the total separation of concerns. To achieve this we introduce container components as additional layer which deals with data and pass it as props to standalone widgets. That’s why the real components with markup in all examples are completely the same. The next code fragment is CartContainer.jsx
from classic FB Flux:
// Facebook Flux's CartContainer
function _getStateFromStores () {
return {
products: CartStore.getAddedProducts(),
total: CartStore.getTotal()
};
}
var CartContainer = React.createClass({
getInitialState: function () {
return _getStateFromStores();
},
componentDidMount: function () {
// subscribe to store changes
CartStore.addChangeListener(this._onChange);
},
componentWillUnmount: function () {
// unsubscribe from stores
CartStore.removeChangeListener(this._onChange);
},
onCheckoutClicked: function () {
if (!this.state.products.length) {
return;
}
// fire action on user interaction
ActionCreators.cartCheckout(this.state.products);
},
render: function () {
return (
<Cart
products={this.state.products}
total={this.state.total}
onCheckoutClicked={this.onCheckoutClicked}
/>
);
},
_onChange: function () {
this.setState(_getStateFromStores());
}
});
All libraries share Flux’s principle which states that stores are the only source of data for components. As you see the thing that change from one solution to another is the difference in subscribe/unsubscribe boilerplate code. Find the differences in the next code fragment from the previous one (except that this one is written in ES6):
// Flummox's CartContainer
let CartContainer = React.createClass({
getInitialState() {
// access actions and stores through Flux instance
this.actions = this.props.flux.getActions('app');
this.cartStore = this.props.flux.getStore('cart');
return this.getStateFromStores();
},
getStateFromStores() {
return {
products: this.cartStore.getProducts(),
total: this.cartStore.getTotal(),
};
},
componentDidMount() {
// subscribe to store changes
this.cartStore.addListener('change', this.onStoreChange);
},
componentWillUnmount() {
// unsubscribe from stores
this.cartStore.removeListener('change', this.onStoreChange);
},
onStoreChange() {
this.setState(this.getStateFromStores());
},
onCheckoutClicked() {
if (!this.state.products.length) {
return;
}
// fire action on user interaction
this.actions.cartCheckout(this.state.products);
},
render() {
return (
<Cart
products={this.state.products}
total={this.state.total}
onCheckoutClicked={this.onCheckoutClicked}
/>
);
}
});
Conclusions
It’s not a surprise that most of the libraries feel very close to each other ‘cause we are talking of the same pattern.
Isomorphic Flux is another topic, which we touched on a bit but it deserves a separate blog post. Before comparison I only saw Fluxible that really deals with server-side contexts per request. However isomorphic examples with Alt and especially Flummox prove that there are easy ways to have isomorphic apps with Flux. And that’s why we need a standalone repo with isomorphic comparison. The example app should be a bit more complex as well, probably with some routing etc.
As a conclusion I would like to say that it was a very intresting experiment. I’ve opened Alt.js and Flummox for myself which I’ve already apart from the crowd. Their APIs look exactly how I imagined some sort of simplified Flux without boilerplate code and some additional cool features too. Thanks everybody who contributed so far and stay tuned with the upcomings!