The Flux Quickstart Guide
This article was authored by Jack Callister and was originally posted at his blog
Concepts
Flux is an architectural pattern for implementing user interfaces. It has three primary concepts; Views, Stores and the Dispatcher. There are also several secondary concepts; Actions, Action Types, Action Creators and Web Utils.
Take your time reading the following definitions then follow the tutorial. Read the definitions again and you’ll be able to start using the Flux pattern in your own applications.
Primary Concepts
Views are React components. They’re responsible for rendering interfaces and handling user events. Their data is obtained from the Stores.
Stores manage data. A single Store manages data for a single domain. When a Store changes its data, it notifies the Views.
The Dispatcher receives new data and passes it to the Stores. Stores update their data (when applicable) and inform the Views.
Secondary Concepts
Actions are the objects passed to the Dispatcher. They contain the new data and an Action Type.
Action Types are the Actions which the system can create. Stores only update their data when Actions with particular Action Types occur.
Action Creators are the objects which build Actions and send them to the Dispatcher or Web Utils.
Web Utils are objects for communicating with external API’s. For example, an Action Creator may invoke requesting new data from the server.
There’s quite a lot to absorb at once. I highly suggest following along with the the starter kit and typing out each line to achieve the best comprehension.
Disclaimer: Use of constants and Web Utils is omitted. This makes understanding Flux simpler and once you’ve grokked the pattern reading the official examples will fill in these secondary concepts.
Views
After getting the starter kit setup (instructions are in the repository) you’ll find the following app.js
file in the src
directory.
var React = require('react');
var Comments = require('./views/comments');
var CommentForm = require('./views/comment-form');
var App = React.createClass({
render: function() {
return (
<div>
<Comments />
<CommentForm />
</div>
);
}
});
React.render(<App />, document.getElementById('app'));
This renders our Views to the DOM. Ignore the Comments
View and focus on implementing the CommentForm
.
var React = require('react');
var CommentActionCreators = require('../actions/comment-action-creators');
var CommentForm = React.createClass({
onSubmit: function(e) {
var textNode = this.refs.text.getDOMNode();
var text = textNode.value;
textNode.value = '';
CommentActionCreators.createComment({
text: text
});
},
render: function() {
return (
<div className='comment-form'>
<textarea ref='text' />
<button onClick={this.onSubmit}>Submit</button>
</div>
);
}
});
module.exports = CommentForm;
The CommentForm
requires a CommentActionCreators
object which is (as the name suggests) an Action Creator.
On form submission the createComment
function is passed a comment
object constructed from the textarea’s value. Let’s build this Action Creator to accept the comment.
Actions
Under the actions
directory create and implement the following comment-action-creators.js
file.
var AppDispatcher = require('../dispatcher/app-dispatcher');
module.exports = {
createComment: function(comment) {
var action = {
actionType: "CREATE_COMMENT",
comment: comment
};
AppDispatcher.dispatch(action);
}
};
The createComment
function builds an Action which contains an Action Type and comment data. This Action is passed to the Dispatcher’s dispatch
function.
Let’s build the Dispatcher to accept Actions.
Note: We could write this code in the View – communicating with the Dispatcher directly. However, it’s best practice to use an Action Creator. It decouples our concerns and provides an single interface for the Dispatcher.
Dispatcher
Under the dispatcher
directory, create and implement the following app-dispatcher.js
file.
var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
A single Dispatcher from the Flux library provides the dispatch
function. Receieved Actions are passed to all of registered callbacks. These callbacks are provided from the Stores.
Note: Since the Dispatcher implementation is hidden, here’s a link to the source.
Stores
Under the stores
directory, create and implement the following comment-store.js
file.
var AppDispatcher = require('../dispatcher/app-dispatcher');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var comments = [];
var CommentStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
},
getAll: function() {
return comments;
}
});
AppDispatcher.register(function(action) {
switch(action.actionType) {
case "CREATE_COMMENT":
comments.push(action.comment);
CommentStore.emitChange();
break;
default:
}
});
module.exports = CommentStore;
There are two segments of code; Store creation and Store registration (with the Dispatcher).
The Store is created by merging an EventEmitter.prototype
object and a custom object, similar to the Dispatcher creation. The EventEmitter.prototype
imbues the Store with the ability to subscribe to and emit events.
The custom object defines public functions for subscribing and unsubscribing to change
events. It also defines a getAll
function which returns the comments
data.
Next, the Store registers a function with the Dispatcher. When the Dispatcher calls dispatch
it passes its argument, which is an Action, to each registered callback function.
In this instance, when an Action is dispatched with an Action Type of CREATE_COMMENT
, the CommentStore
will push the data into its comment array and invoke the emitChange
function.
Now we need a View to display the Store’s comments and subscribe to changes.
Inside the views
directory find the comments.js
file. Amend it to match the following.
var React = require('react');
var CommentStore = require('../stores/comment-store');
function getStateFromStore() {
return {
comments: CommentStore.getAll()
}
}
var Comments = React.createClass({
onChange: function() {
this.setState(getStateFromStore());
},
getInitialState: function() {
return getStateFromStore();
},
componentDidMount: function() {
CommentStore.addChangeListener(this.onChange);
},
componentWillUnmount: function() {
CommentStore.removeChangeListener(this.onChange);
},
render: function() {
var comments = this.state.comments.map(function(comment, index) {
return (
<div className='comment' key={'comment-' + index}>
{comment.text}
</div>
)
});
return (
<div className='comments'>
{comments}
</div>
);
},
});
module.exports = Comments;
Most of this is familiar React code with the addition of requiring the CommentStore
and a few Store related extras.
The getStateFromStores
function retrieves comment data from the Store. This is set as the initial component state within getInitialState
.
Inside componentDidMount
the onChange
function is passed to the Store’s addChangeListener
function. When the Store emits a change
event onChange
will now fire which sets the component’s state as the updated Store data.
Lastly componentWillUnmount
removes the onChange
function from Store.
Conclusion
We now have a working Flux application and have touched on every core concept of the pattern; Views, Stores and the Dispatcher.
- When a user submits a comment, the View invokes an Action Creator.
- The Action Creator builds an Action and passes this to the Dispatcher.
- The Dispatcher sends the Action to the registered Store callback.
- The Store updates its comment’s data and emits a change event.
- The View updates its state from the Store and re-renders.
This is the essence of Flux. The Dispatcher sends data to all Stores which update and notify their Views.
At this point Flux will still feel a little unfamiliar. I highly reccommend reading the official documentation and watching the intro to Flux talk to get a deeper understanding of the Flux architecture and to further ingrain the pattern. I also suggest now reading the official examples.
If I’ve made a mistake or it doesn’t quite click for you, ping me on twitter or better yet make a pull request. Also, feel free to make suggestions for improving this guide.
Jack Callister is the Co-organiser of Hack Pack and a full-stack developer who enjoys building software with Ruby on Rails and React JS. You can visit his blog for more tips and tricks.