React.js comes with a variety of tools and ideas behind it. There are many awesome tools that can work along with React really well. One of the most interesting ideas behind React is how the whole front-end application is structured.

Flux is an interesting approach to structurize front-end apps. It is a relatively simple idea, taking an inspiration from the CQRS architecture. It specifies how your data flows thorough the whole front-end stack. You may heard about it if you are interested in React.

There are lots of libraries which help with building an app with Flux architecture. It may be hard to choose one. They come in different flavors and ideals in mind. Asynchronicity support, immutability as a core, ability to be isomorphic, or more functional approaches are usual ‘ideals’ behind them. I personally was daunted when I tried to choose one.

But an idea is the most important part of the architecture. I would like to show you step-by-step how to create a simple application, backed by a Flux architecture. As a bonus, I’ll show you how to use Immutable.js to improve performance of your React components.

App:

The app presented is a simple todo list. You can add task to it, as well as removing it. You can also see a list of existing tasks. You can see the full flow in a video at the top of this post.

Simplicity of this app would allow you to see a basic flow without distractions.

Why I should use Flux?

Flux with it’s tooling allows us to set data flow in components in a more 'free’ way. Consider a following components’ tree:

<Foo>
  <Bar>
    <Baz />
  <Bar>
  <Abc />
</Foo>

Without Flux, it’s natural that most of state will go to the top-level <Foo> component. This is the only component you have an access to from the outside world. It’s commonly seen that <Foo> component has some kind of setData function defined, just to take data from the outside world. Then it’s passed down in the tree as properties. That extends the root component (<Foo>) responsibilities. Not cool, since such root components tend to grow in terms of code and complexity.

Flux introduces an idea of stores where you keep your data. Any component can register to such store. When data in the store updates, all registered components gets new version of data from the store.

So, without Flux we had:

<Foo> // setData(data) { this.setState(data); }
  <Bar dataBarNeeds={this.state.dataBarNeeds1}>
    <Baz dataBazNeeds={this.state.dataBazNeeds} />
  </Bar>
  <Abc dataAbcNeeds={this.state.dataAbcNeeds} />
</Foo>

And with Flux we have:

<Foo> // Listening for FooDataStore
  <Bar> // Listening for BarDataStore
    <Baz /> // Listening for BazDataStore
  </Bar>
  <Abc /> // Listening for AbcDataStore
</Foo> 

You can listen to as many stores as you like in your component. Stores allow components in deeper levels of components tree to get data directly. It simplifies the flow of data in your application.

Since store data can change, there must be something which triggers the change. That’s why Flux have the concept of actions. Stores subscribe to actions - everytime someone triggers the action, the registered store is notified about it.

The very interesting (and helpful) part of this architecture is that it creates unidirectional data flow. Components gets data from stores and emits actions, which updates stores. In vanilla implementation of Flux there is also a dispatcher - but I won’t cover it in this blogpost.

Enough theory, get to the code!

Starting from scratch - creating an environment

This set of libraries will be used to create the app:

  • Alt - as a library supporting the Flux architecture
  • React-Bootstrap - as a set of ready components that can be used to provide a visually appealing user interface
  • Immutable.js - as a way to store data in an efficient way

First of all, a project must be created. Yeoman can be used to quickly generate such project. To install it, use the following command:

npm install -g yo 

Then, a suitable generator must be installed. For this project, generator-react-webpack is used. Install it:

npm install -g generator-react-webpack

The next step would be to create a project:

yo react-webpack ReactFluxTodo # ReactFluxTodo is an app name.

React-router is not used, so do not enable it. You may be tempted to choose Alt from the list, but don’t do it. It will get installed later in the newest version possible. Use LESS for your stylesheet engine - Bootstrap uses it, so it’ll be easy to use it in this project. As an extension, choose the default.

The generated template uses Grunt to automate development tasks. You may need to install it. To do so, issue the following command:

npm install -g grunt

OK. Unfortunately, this generator comes with a bit outdated version of React and Webpack. You have to update it manually. Open the package.json in an editor of your choice. Then, find a line like this:

"react": "~0.12.2",

This line specifies that React should be installed in a version 0.12.x, where x is higher than 2. Since in this project React 0.13.x will be used, all you need to do is to change it to:

"react": "~0.13.1",

The same treatment must be done with Webpack. Webpack is a tool which compiles your application. It takes your source files and transforms it into an application that can be served by browsers. It supports transforming all files - not only JavaScript files, but also CSS files, fonts and so on. It comes with a server that serves such application during development.

There’s a bug in a bundled version of Webpack that 'breaks’ Alt when updating the source code of React components. This bug is fixed in a newer versions of Webpack. You can read more about it here.

To update Webpack, find it in a package.json file:

"webpack": "~1.4.3",

And change it to:

"webpack": "~1.10.1",

Then run:

npm install

That’s it. You can now run and check the default outcome that was generated by a Yeoman:

grunt serve

A browser should pop up with the current version of the site.

Installing dependencies

Before any line of code is written, Webpack must be configured and dependencies installed to use it. Let’s start with dependencies. React is already installed, so there is nothing to do with it. Alt is not installed. To install it, npm will be used with --save flag. This flag adds Alt as a runtime dependency to this application. It indicates that without Alt, the app won’t run.

npm install --save alt

Now React-Bootstrap must be installed. As can be read in docs, you need to supply a CSS file from Twitter Bootstrap by yourself. Fortunately, an official bootstrap npm package has it. To install it, use the following command:

npm install --save react-bootstrap bootstrap

Last, but not least, Immutable.js must be installed. It can be done the same way that other dependencies are installed:

npm install --save immutable

Since tasks on the todo list will have an unique identifier attached to it, such identifiers must be generated somehow. node-uuid library can be used here:

npm install --save node-uuid

That’s it. All dependencies for this project are installed now!

Configuring Webpack

Webpack is pretty well configured by the Yeoman generator. Unfortunately, you need to tweak it before it can be used with React-Bootstrap. Since Bootstrap comes with variety of font formats (Glyphicons), webpack can’t figure in this configuration what to do with some formats. Fortunately, fix is quick and simple.

You need to edit webpack.config.js and webpack.dist.config.js files, where Webpack config is stored. Webpack uses loaders to know what to do with files it have to serve. To serve static files like fonts or images, it uses the url-loader. It is defined here:

{
  test: /\.(png|jpg|woff|woff2)$/,
  loader: 'url-loader?limit=8192'
}

Bootstrap comes with Glyphicon font in many formats - like eot, ttf or svg. In current configuration, webpack will bail out because it knows nothing about such extensions. Let’s change this:

{
  test: /\.(png|jpg|eot|ttf|svg|woff|woff2)$/,
  loader: 'url-loader?limit=8192'
}

Now Webpack will know what to do with all font formats that come with Bootstrap. Remember to do such change in both files!.

Since you are there, there are two changes that can be done to improve workflow in this project.

  • Right now, the starting point of your app is the React component. Usually you want to separate initialization logic from declaration of your code. In this project, this starting point will be changed.
  • In this project stores will be in a src/stores, actions in src/actions and an Alt instance (described later) in src/lib. To avoid referencing such files via relative paths (like ../something), a top-level aliases will be provided (so you can reference store A in the whole project as stores/A).

Changing the starting point of an application:

This is configured via an entry property within a webpack config. Remember to do this change in both Webpack configuration files (webpack.config.js and webpack.dist.config.js).

Find the entry property in webpack.config.js. It is defined as a list:

entry: [
  'webpack/hot/only-dev-server',
  './src/components/ReactFluxTodo.js'
],

First entry is an implementation detail of a react-hot-loader. This utility comes bundled with this generated project and is responsible for hot reloading your code when it changes. You don’t need to refresh your browser when you change source files thanks to that. You are interested in a second entry. Change it to './src/TodoList.js:

entry: [
  'webpack/hot/only-dev-server',
  './src/TodoList.js'
],

In webpack.dist.config.js there is only one entry ('./src/components/ReactFluxTodo.js'). Just change it to './src/TodoList.js'.

This way webpack will start it’s dependency tree from this file. Create it and paste the following content:

src/TodoList.js:

import React from 'react/addons';
import ReactFluxTodo from 'components/ReactFluxTodo';

React.render(<ReactFluxTodo />, document.getElementById('content')); // jshint ignore:line

Then, remove the line:

React.render(<ReactFluxTodo />, document.getElementById('content')); // jshint ignore:line

From src/components/ReactFluxTodo.js. What you did now is importing React library and the component code and render it. Your code should still work, but now you have extracted initialization out of the component file itself. Requiring it now causes no side effects.

Adding aliases for a todo list project

Webpack stores it’s require aliases in alias property within its config in a resolve subsection. You may see that there are some aliases already defined:

  alias: {
    'styles': __dirname + '/src/styles',
    'mixins': __dirname + '/src/mixins',
    'components': __dirname + '/src/components/'
  }

Just add aliases that are needed in this project in a similar fashion:

  alias: {
    'styles': __dirname + '/src/styles',
    'mixins': __dirname + '/src/mixins',
    'components': __dirname + '/src/components/',
    'stores': __dirname + '/src/stores',
    'actions': __dirname + '/src/actions',
    'lib': __dirname + '/src/lib'
  }

Remember to do changes in both webpack.config.js and webpack.dist.config.js files!

Starting with actions:

To use Alt, you need to create an instance of it. You do so to create some kind of 'namespace’ for all actions and stores you will create later. Usually there is one instance for each application you create, but with complex scenarios you may need more than one. With two instances you have two sets of stores and actions and no way to interact between them. It may be helpful to create a strong boundaries in a really big apps.

To do so, create a file src/lib/AltInstance.js with the following content:

import Alt from 'alt';
export default new Alt();

The code is rather straightforward - an Alt object prototype is imported and a new instance of it is created.

Now you can start defining your actions. I find such start a preferred way to start working with a Flux application. This makes me focus on features, not on the 'infrastructure’ code around it.

In this project our features that user calls by itself are: creating a new task and removing existing ones. Let’s model this in actions!

In src/actions/TodoList.js put the following code:

import UUID        from 'node-uuid';
import Immutable   from 'immutable';
import AltInstance from 'lib/AltInstance';

class TodoListActions {
  addTask(content) { this.dispatch(Immutable.fromJS({ id: UUID.v4(), content })); }
  removeTask(taskID) { this.dispatch(taskID); }
}

export default AltInstance.createActions(TodoListActions);

Notice that set of actions is defined as a pure JavaScript class. Then, it is 'decorated’ with createActions method from Alt instance to allow subscribing to it and dispatching data. Action methods uses this.dispatch to send data to subscribed stores. You can perform a simple validations here.

Todo List store:

There must be a store which consumes actions you defined and keep the data attached to it. Such data will be stored as an immutable list. Immutability is great, because when a React component update, it does not need to re-check all elements of such list to check whether it changed or not. It improves performance of such component.

An example implementation of store can be done in the following way. Put this code in src/stores/TodoList.js:

import ImmutableStore from 'alt/utils/ImmutableUtil';
import { List }       from 'immutable';

import AltInstance    from 'lib/AltInstance';
import Actions        from 'actions/TodoList';

class TodoListStore {
  constructor() {
    let { addTask, removeTask } = Actions;

    this.bindListeners({
      add: addTask,
      remove: removeTask
    });

    this.state = List();
  }

  add(task) {
    return this.setState(this.state.push(task));
  }

  remove(taskID) {
    let taskIndex = this.state.findIndex((task) => task.get('id') === taskID);

    return taskIndex !== (-1) ? this.setState(this.state.delete(taskIndex)) :
                                this.state;
  }
}

export default AltInstance.createStore(ImmutableStore(TodoListStore));

The most important thing happens in a bindListeners method. This is how you subscribe to actions in a store. You delegate an action to a method which handles it. Such method will receive as arguments everything that has been sent by a dispatch within the action.

The second important part is the setState. Since components subscribe to stores, everytime setState is called they will get an update.

Another interesting thing is how this store is created. It is created in a similar way to actions - you create a pure JavaScript object and pass it to an alt instance method to enhance it with subscribing. But with this store it is wrapped in a ImmutableStore function. Why is that?

By default, Alt sets state by mutating the store itself. Since here immutable data structure is used, it can’t be done. Every modification of a List() used here creates a new object - thus a new reference. In Alt you can modify how setState and many other methods work. ImmutableStore function modifies the setState in a way it assigns a new value of the state, not mutating it (using Object.assign). This is a way how Alt provides optional features - you have a full control whether you want to use it or not.

Todo List component

Since actions and a store backing todo list are already done, React components can be created to actually render something.

First of all, provide a TodoListTask component to get a simple view of one task. Put it in src/components/TodoListTask.js:

import React                                from 'react/addons';
import { ListGroupItem, Glyphicon, Button } from 'react-bootstrap';

import TodoListActions                      from 'actions/TodoList';

class TodoListTask extends React.Component {
  constructor(props) {
    super(props);
    this.removeTask = this.removeTask.bind(this);
  }

  removeTask() {
    TodoListActions.removeTask(this.props.task.get('id'));
  }

  render() {
    let { task } = this.props;
    return (<ListGroupItem>
              {task.get('content')}
              <Button bsSize="xsmall" bsStyle="danger" className="pull-right" onClick={this.removeTask}>
                <Glyphicon glyph="remove" />
              </Button>
            </ListGroupItem>);
  }
}

export default TodoListTask;

In this component there is an action called: removeTask. So this button will trigger a store behaviour which is binded to removeTask action. In this case it is a remove method. Now, one-directional data flow is full. It is an example of how things are done in Flux!

A list of tasks have to be listed, though. That means a new component - TodoList must be created. Put this code in src/components/TodoList.js:

import React                    from 'react/addons';
import { Grid, Row, ListGroup } from 'react-bootstrap';
import TodoListStore            from 'stores/TodoList';
import TodoListTask             from 'components/TodoListTask';

class TodoList extends React.Component {
  constructor(props) {
    super(props);

    let { shouldComponentUpdate } = React.addons.PureRenderMixin;

    this.shouldComponentUpdate    = shouldComponentUpdate.bind(this);
    this.state                    = { tasks: TodoListStore.getState() };
    this.listChanged              = this.listChanged.bind(this);
  }

  componentDidMount()    { TodoListStore.listen(this.listChanged); }
  componentWillUnmount() { TodoListStore.unlisten(this.listChanged); }

  listChanged(taskList)  { this.setState({ tasks: taskList }); }

  render() {
    let {tasks} = this.state;

    return (
      <Grid>
        <Row fluid={true}>
          <h1>Tasks:</h1>
          <ListGroup>
            {tasks.map(task =>
              <TodoListTask key={task.get('id')} task={task} />
             ).toJS()}
          </ListGroup>
        </Row>
      </Grid>
    );
  }
}

export default TodoList;

Notice how TodoList component connects to the store. First of all, in the component’s contructor there is a call to getState. It gets the current state stored in a TodoListStore. Then, when a component mounts there is a listen method of a store used. From now every call to setState within a store will trigger the listChanged method of a component.

The last part of integration is to specify what happens when component gets unmounted. Otherwise you’d get errors, because even unmounted stores would call setState, breaking React invariants. It is done within the componentWillUnmount lifecycle method.

Another interesting thing happens within a render method itself. While React can iterate through everything that has an iterator interface (and Immutable.js List has an Iterator interface), it would be a warning, since deprecated method would get called. To overcome this problem, after mapping tasks to TodoListTask components it is transformed into a simple mutable list using toJS method.

Also, there is a PureRenderMixin used. It makes the component more performant by adding an assumption that your data is immutable. This is a case with an approach presented in this app.

Since this component should be displayed, it is a good moment to clear out the index.html file a little. Replace:

<div id="content">
  <h1>If you can see this, something is broken (or JS is not enabled)!!.</h1>
</div>

With:

<div id="todo-list">
  <p>Sorry, but this todo list needs JavaScript enabled to work.</p>
</div>

Now id of the <div> within a component rendered will make a little more sense.

Then, change your src/TodoList.js to:

'use strict';

require('bootstrap/less/bootstrap.less');

import React    from 'react/addons';
import TodoList from 'components/TodoList';

React.render(<TodoList />, document.getElementById('todo-list'));

You can remove src/components/ReactFluxTodo.js since it is not needed anymore. Notice that there is a LESS file required - Webpack will handle compilation and serving this LESS file for you!

Yet another full Flux cycle - adding a new task

So now the app knows how to display a list of tasks and how to remove one task from it. What it lacks is an ability to add a new task. Pattern that Flux provides - defining Actions, connecting them to Stores, listening for Stores in Components and emitting Actions from them is repeatable thorough the app designed in a Flux architecture. That’s why this feature starts in a similar way.

In the adding a new task form there will be two actions - clearing the form (when the form finishes its job) and handling input change. Let’s model it. Put this code in src/actions/AddNewTaskForm.js:

import AltInstance from 'lib/AltInstance';

class AddNewTaskFormActions {
  changeContent(content) {
    this.dispatch(content);
  }

  clearForm() {
    this.dispatch();
  }
}

/* If your actions are as simple as just dispatching passed values, you can use a slightly different (and more concise) API for such use case:
 * export default alt.generateActions('changeContent', 'clearForm');
 */

export default AltInstance.createActions(AddNewTaskFormActions);

In the comment there is a handy syntactic sugar for creating very simple actions like this. For consistency it is omitted in this example.

Now, to the store. What store should do? I think working with form validations on the store side is very Fluxy way to design forms. That’s why this logic is there. Put this code in src/stores/AddNewTaskForm.js:

import AltInstance from 'lib/AltInstance';
import Actions     from 'actions/AddNewTaskForm';

class AddNewTaskFormStore {
  constructor() {
    this.validationError = '';
    this.content = '';
    this.submittable = false;

    let { changeContent, clearForm } = Actions;
    this.bindListeners({ changeContent, clearForm });
  }

  changeContent(newContent) {
    let validationError = this.validate(newContent),
        submittable     = validationError.length === 0;

    this.setState({ validationError,
                    content: newContent,
                    submittable });
  }

  clearForm() { this.setState({ validationError: '',
                                content: '',
                                submittable: false }); }

  validate(newContent) {
    return (newContent.length > 3) ? '' : 'Task content have to be longer than 3 characters.';
  }
}

export default AltInstance.createStore(AddNewTaskFormStore);

Notice that there is no ImmutableStore function used at all. Since only primitive values are used in this store, it is not needed. In this approach store data is defined simply as a fields of the store. In an ImmutableStore approach your data must be stored within a state field of your store.

Then a component must be created. Put this in src/components/AddNewTaskForm.js:

import React                 from 'react/addons';
import { Input, Button }     from 'react-bootstrap';

import AddNewTaskFormActions from 'actions/AddNewTaskForm';
import TodoListActions       from 'actions/TodoList';

import AddNewTaskFormStore   from 'stores/AddNewTaskForm';

class AddNewTaskForm extends React.Component {
  constructor(props) {
    super(props);

    let { shouldComponentUpdate } = React.addons.PureRenderMixin;

    this.state = AddNewTaskFormStore.getState();

    this.shouldComponentUpdate = shouldComponentUpdate.bind(this);
    this.formChanged = this.formChanged.bind(this);
    this.validationClass = this.validationClass.bind(this);
    this.submit = this.submit.bind(this);
  }

  componentDidMount()    { AddNewTaskFormStore.listen(this.formChanged); }
  componentWillUnmount() { AddNewTaskFormStore.unlisten(this.formChanged); }

  formChanged(formState) { this.setState(formState); }
  changeContent(ev)      { AddNewTaskFormActions.changeContent(ev.target.value); }
  submit(ev) {
    ev.preventDefault();
    if(!this.state.submittable) { return; }

    TodoListActions.addTask(this.state.content);
    AddNewTaskFormActions.clearForm();
  }

  validationClass() {
    return {
      true: 'error',
      false: undefined
    }[!!this.state.validationError.length];
  }

  render() {
    return (
      <form onSubmit={this.submit}>
        <Input
          key="taskContent"
          type="text"
          value={this.state.content}
          placeholder="4+ characters..."
          label="Enter content:"
          bsStyle={this.validationClass()}
          help={this.state.validationError}
          hasFeedback
          onChange={this.changeContent} />
        <Button key="submitButton" type="submit"
                bsStyle="primary" disabled={!this.state.submittable}>Submit</Button>
      </form>
    );
  }
}

export default AddNewTaskForm;

React Bootstrap input component have a built-in way to work with validations. That’s a great help here - it makes this component quite simple. There are helper methods which 'transforms’ validation data to CSS classes and so on. There is an interesting approach with hash syntax presented there, which helps avoiding if’s in code, making the component more 'declarative’. The form gets its state straight from the store - there is no internal state whatsoever.

To finish this feature, newly created component must be added to a top-level component. After </ListGroup> in a TodoList component, put the following code:

          <h2>Add new task:</h2>
          <AddNewTaskForm />

Also, new component must be imported. In src/components/TodoList.js, after:

import TodoListTask             from 'components/TodoListTask';

Add the following code:

import AddNewTaskForm           from 'components/AddNewTaskForm';

That’s it. The Todo list app is complete!

Repository:

There is a GitHub repository which contains the example described above. It is made in around 25~ commits. If you want to see how it was created in a step-by-step manner with some rationale behind decisions, check out the React-Flux-Alt-Immutable-TodoList repo.

Read more:

comments powered by Disqus