Beginners guide to Flux with React

Feb 19, 2015

I’ve recently started learning React and now Flux. I thought I would make a simple post about explaining how Flux works. For this I will use the Flux Todo list app Facebook have on their website, but try to explain the Flux flow of data.

The Flux flow

The best way to describe the flow of Flux is this diagram:

Diagram of flux flow

There are other, more complicated diagrams, such as that on the Github page. But for now the above is all we need to worry about.

What is a…

Firstly, a little one description about what each ‘component’ does:

Action: Sends ‘actions’ to the dispatcher
Dispatcher: Dispatches actions to the stores
Store: Stores data, catches actions and updates data accordingly
View: Renders data, and creates actions

How it works

In the Todo app there are no actions to set the app up as all data is kept locally. If there was a database of Todo’s elsewhere then we would have an action to connect to the server and retrieve the Todos and then dispatch the information for the TodoStore to deal with.

TodoStore holds a private todos object which will be used to store the todos locally in the app.

router.js
1var _todos = {};

This is important as this means we have to use an action to update/delete a todo. But we can access the todos via the public api of TodoStore:

router.js
1var TodoStore = assign(
2 {},
3 EventEmitter.prototype, {
4 getAll: function() {
5 return _todos;
6 },
7 }
8);

The TodoApp.react component/view gets the todos via the getInitialState() function that is part of React.

getInitialState() executes exactly once during the lifecycle of the component and sets up the initial state of the component.

router.js
1function getTodoState() {
2 return {
3 allTodos: TodoStore.getAll(),
4 areAllComplete: TodoStore.areAllComplete()
5 };
6}
7
8var TodoApp = React.createClass({
9 getInitialState: function() {
10 return getTodoState();
11 }
12
13 ...
14});

This calls the TodoStore.getAll() function that was mentioned above which saves the Todos into the state of the TodoApp component.

This is then rendered into the page by React:

router.js
1render: function() {
2 return (
3 <div>
4 <Header />
5 <MainSection allTodos={this.state.allTodos} areAllComplete={this.state.areAllComplete} />
6 <Footer allTodos={this.state.allTodos} />
7 </div>
8 );
9},

When you first load up the app you will see no Todos. This is because the todos are only local and so it has no been populated yet.

To populate the Todo we use an Action. In the case of this app it will be caused by the onSave function which is passed to the TodoTextInput component. Which then calls this parent function with the value that the user types in on the onBlur method.

router.js
1var Header = React.createClass({
2 render: function() {
3 return (
4 <TodoTextInput id="new-todo" placeholder="What needs to be done?" onSave={this._onSave} />
5 );
6 },
7
8 _onSave: function(text) {
9 if (text.trim()) {
10 TodoActions.create(text);
11 }
12 }
13});

This then calls an action, defined here in TodoActions. An action is a method that sends an actionType through the dispatcher that the stores can listen to, and which can also pass information.

TodoActions is an object of functions which can be called when you have included the file. As seen before, our onSave method calls the TodoActions.create(text) method.

router.js
1var TodoActions = {
2 create: function(text) {
3 AppDispatcher.dispatch({ actionType: TodoConstants.TODO_CREATE, text: text });
4 }
5}

What this does it calls the dispatch method of the AppDispatcher (which we don’t really need to worry about as we are using Facebook’s own open source one). We pass an actionType, in this case we pass an Enum constant (done by the KeyMirror Node package) to describe the actionType. The actionType we send is TODO_CREATE, which we use to notify that we want to create a new todo. And we also pass throught the text of the new Todo.

In our TodoStore we then register a callback to the AppDispatcher. If you are interested in how this works then I found this source code to be helpful.

router.js
1AppDispatcher.register(function(action) {
2 var text;
3 switch(action.actionType) {
4 case TodoConstants.TODO_CREATE:
5 text = action.text.trim();
6
7 if (text !== '') {
8 create(text);
9 }
10
11 TodoStore.emitChange();
12 break;
13 }
14});

The callback we pass in also takes a parameter, action, which we use in a switch statement (in this case). The switch statement evaluates the actionType of the action passed into it. Then, depending on the actionType (which we check using the Enum constant we send in the action itself) we decide what to do with the action and the data we’re sent (if any).

In this case we catch the TODO_CREATE action, do some manipulation on the text (that we access via action.text (a property of action)). Then it will call the private create method passing in text, which will update the list of todos (held in the TodoStore).

The last line is quite important:

router.js
1TodoStore.emitChange();

This calls this method in the TodoStore:

router.js
1emitChange: function() { this.emit(CHANGE_EVENT); }

This uses Nodes EventEmitter to emit the CHANGE_EVENT to any component that is listening to it. In our TodoApp component we do this by adding a change listener when the component mounts.

router.js
1componentDidMount: function() { TodoStore.addChangeListener(this._onChange); }

TodoStore adds as a listener on the server. More information can be seen on the event emitter API docs.

router.js
1addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }

The change listener we add through TodoApp adds the callback of this._onChange:

router.js
1_onChange: function() { this.setState(getTodoState()); }

This simply calls the setState method which we saw above which updates the state of the page and causes React to re-render the page if the data/state is different.

Contact.

LET'S WORK

TOGETHER

I am open to both contract and freelance developer projects and would love to hear what your ideas are.

Feel free to drop me an email and I'll let you know how I can help you!

mail@benstokoe.co.uk