The JavaScript community has been seeing a huge number of new technologies and tools in recent years. One of the current hottest topics is React, a library for building user interfaces, created and open-sourced by Facebook.
One of React’s features that sets it apart from other popular JS libraries is that it’s not trying to give you a complete recipe for building frontend application. Instead, it focuses on doing just one thing - building user interfaces, and does it incredibly well.
By utilizing Virtual DOM and one-way data flow, React facilitates building performant, scalable and maintainable graphical user interfaces.
The fact, that React doesn’t make any assumptions about the rest of your technology stack, and the flexibility that it allows, are arguably one of its strengths. Depending on your needs, you can use React alone, you can use it as a V in MVC or in any other configuration that will suit you. While being able to hand-pick parts of your technology stack can be very tempting, in many cases it’s more beneficial to have a well-established and battle-proven solution available.
Flux to the rescue!
First thing you need to know about Flux is that it’s not a framework. Flux can be more accurately described as a pattern or an architecture. In the next part of the article we will first examine the pattern itself, and then have a look at two of its variants: reference implementation as described by Facebook and Reflux, authored by Mikael Brassman.
Ingredients of Flux
There are three major ingredients that Flux architecture consists of: views, dispatcher and stores. Let’s have a closer look at each of them. The picture below explains how these pieces fit into the big picture (source: facebook.github.io/flux)
Views
Views are good old React components. There’s nothing unusual about them. Thanks to the fact that React was built from the beginning without any assumptions about the libraries it would be used with, there are no major changes needed to make the standard components work with Flux.
Dispatcher
The dispatcher is a central element of the Flux architecture. Its responsibility is distributing actions to the stores. Each store registers itself in the dispatcher and provides a callback. Whenever a new action enters the dispatcher, all the stores are notified about it through the registered callback. It’s important to note, that dispatcher isn’t smart enough to determine which stores might be interested about the action. It simply invokes each registered callback, passing it the action, and it’s store’s job to decide whether it should act upon the action or just ignore it. An additional job that dispatcher can handle, useful in more complex applications, is managing dependencies between stores. It does it by controlling the order in which stores are being notified about new actions. For example, if store A depends on store B, it can wait for the store B to update before updating itself.
Stores
Stores are where the application’s state and logic lives.
Rather than representing a single entity (like, for example, ActiveRecord models), they are used to interact with whole collections of objects.
The store’s state is updated when the actions occur in the application.
As described earlier, each store registers a callback in the dispatcher. Every action that takes place is then sent to this callback.
Inside a callback, the type
property is used to decide if the action should cause the store to update its state.
If the state was updated, it is propagated to views and reflected in the user interface.
In addition to three basic components, there are two more elements that deserve to be mentioned:
Actions
An action is a simple object, containing type
property and optional payload.
{
type: "CreatePost",
title: title,
text: text
}
Actions might be created either as a result of user interacting with the website, or when an event comes from the server.
Controller-Views
Controller-Views are special type of views, living close to the top of hierarchy of React components. They listen to stores’ updates and when they occur - fetch changes from the store and pass them to their descendants.
Implementations
As mentioned above, Flux is not a framework, but a pattern that you can implement in your application yourself. However, there are already a few libraries that you can use to save yourself from writing all the boilerplate code. These libraries don’t always follow the original architecture very strictly. Instead, they often include modifications that, according to their authors, fix some of the issues that can be found in original Flux concept.
Because of the relative freshness of technology, new variations of Flux keep popping up all the time and we can expect many more to come. However, there are already a few examples of well-established libraries. One of such examples is Reflux, which we will examine and compare with original Flux in the next part of the article.
To compare these implementations, we will take a look at a sample application implemented with each of them. The application will be a very simple budget management tool. It will allow its users to add entries for incomes and outcomes and display balance (optionally filtered by category). The code can be found on GitHub.
Original Flux
Before we start dwelling on libraries created by the community, let’s first have a look at the original Flux architecture, as described by its creators at Facebook (link).
The central point of the app in the reference implementation is the Dispatcher. Every action flows through it, before reaching stores and causing update of the state. Luckily, we don’t need to implement it ourselves, since the dispatcher used by Facebook in production is available via npm, Bower and GitHub.
In our sample application, we import it in a following way:
// javascript/dispatcher/budget_app_dispatcher.js
var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
Once we have the dispatcher in place, we can define actions. We create an EntryActions
object, which groups action creators.
Calling action creator results in passing the object to the dispatcher, from where it can propagate further.
Such object must contain one attribute defining type of action (actionType
) and arbitrary number of additional attributes.
// javascript/actions/entry_actions.js
var AppDispatcher = require('../dispatcher/budget_app_dispatcher');
var Constants = require('../constants/budget_app_constants');
var EntryActions = {
create: function(title, amount, category) {
AppDispatcher.dispatch({
actionType: Constants.ENTRY_CREATE,
title: title,
amount: amount,
category: category
});
},
// ...........
};
Last but not least, we need to create a store for keeping and manipulating the list of entries.
First, we define functions that will operate on the _entries
object, which keeps a list of all existing income and outcome records:
// javascript/stores/entry_store.js
var _entries = {};
function create(title, amount, category) {
var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
_entries[id] = {
id: id,
title: title,
amount: amount,
category: category,
timestamp: (new Date())
};
}
function update(id, updates) {
_entries[id] = assign({}, _entries[id], updates);
}
function destroy(id) {
delete _entries[id];
}
The store emits change
event using EventEmitter every time the list of entries is updated.
This way it propagates changes to the views.
// javascript/stores/entry_store.js
var CHANGE_EVENT = 'change';
var EntryStore = assign({}, EventEmitter.prototype, {
getAll: function() {
return _entries;
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
Next, the store registers a callback within the dispatcher. From now on, it will be notified about every action that goes through the dispatcher.
It will use a actionType
attribute of the action object to decide whether it should update its state in response to the action or ignore it.
It’s achieved by a simple switch
statement:
// javascript/stores/entry_store.js
AppDispatcher.register(function(action) {
switch(action.actionType) {
case Constants.ENTRY_CREATE:
create(action.title, action.amount, action.category);
EntryStore.emitChange();
break;
case Constants.ENTRY_UPDATE:
update(action.id, action.updates);
EntryStore.emitChange();
break;
case Constants.ENTRY_DESTROY:
destroy(action.id);
EntryStore.emitChange();
break;
default:
// no op
}
});
Finally, React components can register listeners in the store. A right place for registering the lsitener is componentDidMount
lifecycle method.
We should also remember about unregistering the listener before the component is unmounted; we can use componentWillUnmount
method for this purpose.
// javascript/components/budget_app.jsx
function getEntriesState() {
return {
entries: EntryStore.getAll()
};
}
var BudgetApp = React.createClass({
getInitialState: function() {
return getEntriesState();
},
componentDidMount: function() {
EntryStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
EntryStore.removeChangeListener(this._onChange);
},
_onChange: function() {
this.setState(getEntriesState());
},
// ...........
});
Calling actions looks as follows:
// javascript/components/entry_row.jsx
var EntryActions = require('../actions/entry_actions');
var EntryRow = React.createClass({
_onRemoveClick: function() {
EntryActions.destroy(this.props.entry.id);
},
render: function () {
var entry = this.props.entry;
return (
<tr>
<td>{entry.title}</td>
<td className={entry.amount > 0 ? 'success' : 'danger'}>{entry.amount.toFixed(2)}</td>
<td>{entry.category}</td>
<td>{entry.timestamp.toLocaleFormat()}</td>
<td><a href="#" onClick={this._onRemoveClick}>REMOVE</a></td>
</tr>
);
}
});
That’s all it takes to build a very minimal, but working application using the reference Flux implementation.
Reflux
One of the most popular implementations of Flux pattern is Reflux. While it shares many similarities with standard Flux, there are also fundamental differences between the two:
- lack of the singleton Dispatcher; instead, each action acts like a dispatcher
- lack of action creators - actions are functions themselves
- stores listen to actions; this means that they are no longer receiving all the actions that occur in the application and filter them with a switch statement
- stores can listen to other stores, allowing data aggregation
Let’s see how these changes are reflected in the actual code.
As mentioned above, Reflux doesn’t use dispatcher, so we will jump straight to defining the actions:
// javascript/actions/entry_actions.js
var EntryActions = Reflux.createActions([
"entryCreate",
"entryUpdate",
"entryDestroy"
]);
The store definition is also much more concise than before:
// javascript/stores/entry_store.js
var Reflux = require('reflux');
var EntryActions = require('../actions/entry_actions');
var assign = require('object-assign');
var _entries = {};
var EntryStore = Reflux.createStore({
listenables: [EntryActions],
getInitialState: function() {
return _entries;
},
entryCreate: function(title, amount, category) {
var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
_entries[id] = {
id: id,
title: title,
amount: amount,
category: category,
timestamp: (new Date())
};
this.trigger(_entries);
},
entryUpdate: function(id, updates) {
_entries[id] = assign({}, _entries[id], updates);
this.trigger(_entries);
},
entryDestroy: function(id) {
delete _entries[id];
this.trigger(_entries);
}
});
Here, we make use of listenables
property. When passing Actions object to this property, we tell the store to listen to all the actions defined in this object
and call method with matching names when such action occurs. There’s no need for the nasty switch
statement.
The views listening to the store are notified on changes by the trigger
method.
Listening to store’s state changes in React components is very easy using Reflux.connect
method.
We need to add just one line inside the component definition:
// javascript/components/budget_app.jsx
var BudgetApp = React.createClass({
mixins: [Reflux.connect(EntryStore, 'entries')],
// .........
The list of entries can be accessed inside the component via this.state.entries
.
Calling actions from within the component looks exactly the same as in reference implementation:
// javascript/components/entry_row.jsx
var EntryRow = React.createClass({
_onRemoveClick: function() {
EntryActions.entryDestroy(this.props.entry.id);
},
render: function () {
var entry = this.props.entry;
return (
<tr>
<td>{entry.title}</td>
<td className={entry.amount > 0 ? 'success' : 'danger'}>{entry.amount.toFixed(2)}</td>
<td>{entry.category}</td>
<td>{entry.timestamp.toLocaleFormat()}</td>
<td><a href="#" onClick={this._onRemoveClick}>REMOVE</a></td>
</tr>
);
}
});
Summary
As the above example shows, differences between the two implementations have significant effect on the produced code. Reflux is arguably more concise and streamlined. It saves us from typing a lot of repetitive code. In my opinion it also smoothens the learning curve.
The biggest selling point of reference implementation is the fact, that it’s backed by Facebook. This makes it more likely that the project will be maintained and actively developed.
Post by Kamil Bielawski
Kamil has been working with AmberBit since 2012, and is an expert on JavaScript and Ruby, recently working with Elixir code as well.