Getting Started with React, Redux and Immutable: a Test-Driven Tutorial (Part 1)

Image credits: egghead.io

A few weeks ago, I was idly browsing through Hacker News, and read a headline about Redux, which I understood was yet another thing that was supposed to get along well with React. Javascript fatigue had already got its grip on me, so I paid little attention, until I read the following features of Redux:

  • It enforces functional programming and ensures predictability of the app behavior
  • It allows for isomorphic app, where most of the logic is shared between the client and the server code
  • A time-traveling debugger?! Is that even possible?

It seemed like an elegant solution to manage the state of React applications, plus who would say no to time travel? So I got in, read the examples in the docs and this fantastic tutorial by @teropa: A Comprehensive Guide to Test-First Development with Redux, React, and Immutable (which is a major source of inspiration for this article).

I liked it. The code is elegant. The debugger is insanely great. I mean – look at this (click to see in action):

time-travel

What follows is the first part of a tutorial that will hopefully guide you to the principles of the Redux way of doing things©. It is purposefully limited in scope (it’s client-side only, so no isomorphism; a quite simplistic app) in order to keep it somewhat concise. If you want to dig further, I can only recommend the tutorial mentioned above. A companion GitHub repo is available here, which follows the steps to the final app and has a copy of this article. If you have any questions/suggestions on the code and/or the turorial, please leave a comment or – better yet, open a Pull Request!

Edit: The article was updated to use the ES2015 syntax for React classes. Thanks to seantimm for pointing that out in the comments!

The app

For the purpose of this tutorial we will build the classic TodoMVC app. For the record, the requirements are the following:

  • Each todo can be active or completed
  • A todo can be added, edited or deleted
  • Todos can be filtered by their status
  • A counter of active todos is displayed at the bottom
  • Completed todos can be deleted all at once

You can see an example of such an app here.

Redux and Immutable: functional programming to the rescue

A few months back, I was developing a webapp consisting of dashboards. As the app grew, we noticed more and more pernicious bugs, that were hard to corner and fix. Things like “if you go to this page, click on that button, then go back to the home page, grab a coffee, go to this page and click twice here, something weird happens”. The source of all these bugs was either side effects in our code or logic: an action could have an unwanted impact on something somewhere else in our app, that we were not aware of.

That is where the power of Redux lies: the whole state of the app is contained in a single data structure, the state tree. This means that at every moment, what is displayed to the user is the only consequence of what is inside the state tree, which is the single source of truth. Every action in our app takes the state tree, apply the corresponding modifications (add a todo, for example) and outputs the updated state tree, which is then rendered to the user. There is no obscure side effects, no more references to a variable that was inadvertantly modified. This makes for a cleaner separation of concerns, a better app structure and allows for much better debugging.

Immutable is a helper library developed by Facebook that provides tools to create and manipulate immutable data structures. Although it is by no means mandatory to use it alongside Redux, it enforces the functional approach by forbidding objects modifications. With Immutable, when we want to update an object, we actually create another one with the modifications, and leave the original one as is.

Here is an example drawn from the docs:

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1 === map2); // no change
var map3 = map1.set('b', 50);
assert(map1 !== map3); // change

We updated a value of map1, the map1 object in itself remained identical and a new object, map3, was created.

Immutable will be used to store the state tree of our app, and we will soon see that it provides a lot of simple methods to manipulate it concisely and efficiently.

Setting up the project

Disclaimer: a lot of the setting up is inspired by the @teropa tutorial mentionned earlier

Note: it is recommended to follow this project with a version of NodeJS >= 4.0.0. You can install nvm (node version manager) to be able to switch between Node versions with ease.

Note: here is the relevant commit in the companion repository.

It is now time to setup the project:

mkdir redux-todomvc
cd redux-todomvc
npm init -y

The project directory will look like the following:

├── dist
│   ├── bundle.js
│   └── index.html
├── node_modules
├── package.json
├── src
├── test
└── webpack.config.js

First, we write a simple HTML page in which will run our application:

dist/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>React TodoMVC</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

To go along with it, let’s write a very simple script that will tell us that everything went fine with the packaging:

src/index.js

console.log('Hello World!');

We are going to build the packaged bundle.js file using Webpack. Among the advantages of Webpack feature speed, ease of configuration and most of all hot reload, i.e. the possibility for the webpage to take into account our latest changes without even reloading, meaning the state for the app is kept across (hot) reloads.

Let’s install webpack:

npm install -g --save-dev webpack@1.12.14 webpack-dev-server@1.14.1

The app will be written using the ES2015 syntax, which brings along an impressive set of new features and some nicely integrated syntactic sugar. If you would like to know more about ES2015, this recap is a neat resource.

Babel will be used to transpile the ES2015 syntax to common JS:

npm install --save-dev babel-core@6.5.2 babel-loader@6.2.4 babel-preset-es2015@6.5.0

We are also going to use the JSX syntax to write our React components, so let’s install the Babel React package:

npm install --save-dev babel-preset-react@6.5.0

Here we configure webpack to build our upcoming source files:

package.json

"babel": {
  "presets": ["es2015", "react"]
}

webpack.config.js

module.exports = {
  entry: [
    './src/index.js'
  ],
  module: {
    loaders: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel'
    }]
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  output: {
    path: __dirname + '/dist',
    publicPath: '/',
    filename: 'bundle.js'
  },
  devServer: {
    contentBase: './dist'
  }
};

Now, let’s add React and React Hot Loader to the project:

npm install --save react@0.14.7 react-dom@0.14.7
npm install --save-dev react-hot-loader@1.3.0

In order to enable the hot loading, a few changes are necessary in the webpack config file:

webpack.config.js

var webpack = require('webpack'); // Requiring the webpack lib

module.exports = {
  entry: [
    'webpack-dev-server/client?http://localhost:8080', // Setting the URL for the hot reload
    'webpack/hot/only-dev-server', // Reload only the dev server
    './src/index.js'
  ],
  module: {
    loaders: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'react-hot!babel' // Include the react-hot loader
    }]
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  output: {
    path: __dirname + '/dist',
    publicPath: '/',
    filename: 'bundle.js'
  },
  devServer: {
    contentBase: './dist',
    hot: true // Activate hot loading
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin() // Wire in the hot loading plugin
  ]
};

Setting up the unit testing framework

We will be using Mocha and Chai as our test framework for this app. They are widely used, and the output they produce (a diff comparison of the expected and actual result) is great for doing test-driven-development. Chai-Immutable is a chai plugin that handles immutable data structures.

npm install --save immutable@3.7.6
npm install --save-dev mocha@2.4.5 chai@3.5.0 chai-immutable@1.5.3

In our case we won’t rely on a browser-based test runner like Karma – instead, the jsdom library will setup a DOM mock in pure javascript and will allow us to run our tests even faster:

npm install --save-dev jsdom@8.0.4

We also need to write a bootstrapping script for our tests that takes care of the following:

  • Mock the document and the window objects, normally provided by the browser
  • Tell chai that we are using immutable data structures with the package chai-immutable

test/setup.js

import jsdom from 'jsdom';
import chai from 'chai';
import chaiImmutable from 'chai-immutable';

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
const win = doc.defaultView;

global.document = doc;
global.window = win;

Object.keys(window).forEach((key) => {
  if (!(key in global)) {
    global[key] = window[key];
  }
});

chai.use(chaiImmutable);

Let’s update the npm test script so that it takes into account our setup:

package.json

"scripts": {
  "test": "mocha --compilers js:babel-core/register --require ./test/setup.js 'test/**/*.@(js|jsx)'",
  "test:watch": "npm run test -- --watch --watch-extensions jsx"
},

Edit: It seems that the npm run test:watch command does not work on Windows. If you encounter this problem, please refer to this issue in the GitHub repository

Now, if we run npm run test:watch, all the .js or .jsx file in our test directory will be run as mocha tests each time we update them or our source files.

The setup is now complete: we can run webpack-dev-server in a terminal, npm run test:watch in another, and head to localhost:8080/ in a browser to check that Hello World! appears in the console.

Building a state tree

As mentionned before, the state tree is the data structure that will hold all
the information contained in our application (the state). This structure needs
to be well thought of before actually developing the app, because it will shape
a lot of the code structure and interactions.

As an example here, our app is composed of several items in a todo list:

state_tree1

Each of these items have a text and, for an easier manipulation, an id.
Moreover, each item can have one of two status – active or completed:
Lastly, an item can be in a state of edition (when the user wants to edit the
text), so we should keep track of that as well:

state_tree2

It is also possible to filter our items based on their statuses, so we can add a
filter entry to obtain our final state tree:

state_tree3

Writing the UI for our app

todo-app-structure

First of all, we are going to split the app into components:

  • The TodoHeader component is the input for creating new todos
  • The TodoList component is the list of todos
  • The TodoItem component is one todo
  • The TextInput component is the input for editing a todo
  • The TodoTools component displays the active counter, the filters and the “Clear completed” button
  • The Footer component displays the footer info and has no logic attached to it

We are also going to create a TodoApp component that will hold all the others.

Bootstrapping our first component

Note: here is the relevant commit in the companion repository.

As we saw, we are going to put all of our components in a single one, TodoApp. so let’s begin by attaching this component to the #app div in our index.html:

src/index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import {List, Map} from 'immutable';

import TodoApp from './components/TodoApp';

const todos = List.of(
  Map({id: 1, text: 'React', status: 'active', editing: false}),
  Map({id: 2, text: 'Redux', status: 'active', editing: false}),
  Map({id: 3, text: 'Immutable', status: 'completed', editing: false})
);

ReactDOM.render(
  <TodoApp todos={todos} />,
  document.getElementById('app')
);

As we used the JSX syntax in the index.js file, we have to change its extension to .jsx, and change the file name in the webpack config file as well:

webpack.config.js

entry: [
  'webpack-dev-server/client?http://localhost:8080',
  'webpack/hot/only-dev-server',
  './src/index.jsx' // Change the index file extension
],

Writing the todo list UI

Now, we are going to write a first version of the TodoApp component, that will display the list of todo items:

src/components/TodoApp.jsx

import React from 'react';

export default class TodoApp extends React.Component {
  getItems() {
    return this.props.todos || [];
  }
  render() {
    return <div>
      <section className="todoapp">
        <section className="main">
          <ul className="todo-list">
            {this.getItems().map(item =>
              <li className="active" key={item.get('text')}>
                <div className="view">
                  <input type="checkbox"
                         className="toggle" />
                  <label htmlFor="todo">
                    {item.get('text')}
                  </label>
                  <button className="destroy"></button>
                </div>
              </li>
            )}
          </ul>
        </section>
      </section>
    </div>
  }
};

Two things come to mind.

First, if you look at the result in your browser, it is not that much appealing. To fix that, we are going to use the todomvc-app-css package that brings along all the styles we need to make this a little more enjoyable:

npm install --save todomvc-app-css@2.0.4
npm install style-loader@0.13.0 css-loader@0.23.1 --save-dev

We need to tell webpack to load css stylesheets too:

webpack.config.js

// ...
module: {
  loaders: [{
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loader: 'react-hot!babel'
  }, {
    test: /\.css$/,
    loader: 'style!css' // We add the css loader
  }]
},
//...

Then we will include the style in our index.jsx file:

src/index.jsx

// ...
require('../node_modules/todomvc-app-css/index.css');

ReactDOM.render(
  <TodoApp todos={todos} />,
  document.getElementById('app')
);

The second thing is that the code seems complicated: it is. That is why we are going to create two more components: TodoList and TodoItem that will take care of respectively the list of all the items and a single one.

Note: here is the relevant commit in the companion repository.

src/components/TodoApp.jsx

import React from 'react';
import TodoList from './TodoList'

export default class TodoApp extends React.Component {
  render() {
    return <div>
      <section className="todoapp">
        <TodoList todos={this.props.todos} />
      </section>
    </div>
  }
};

The TodoList component will display a TodoItem component for each item it has received in its props:

src/components/TodoList.jsx

import React from 'react';
import TodoItem from './TodoItem';

export default class TodoList extends React.Component {
  render() {
    return <section className="main">
      <ul className="todo-list">
        {this.props.todos.map(item =>
          <TodoItem key={item.get('text')}
                    text={item.get('text')} />
        )}
      </ul>
    </section>
  }
};

src/components/TodoItem.jsx

import React from 'react';

export default class TodoItem extends React.Component {
  render() {
    return <li className="todo">
      <div className="view">
        <input type="checkbox"
               className="toggle" />
        <label htmlFor="todo">
          {this.props.text}
        </label>
        <button className="destroy"></button>
      </div>
    </li>
  }
};

Before going more deeply into possible user actions and how we are going to integrate them in the app, let’s add an input in the TodoItem component for editing:

src/components/TodoItem.jsx

import React from 'react';

import TextInput from './TextInput';

export default class TodoItem extends React.Component {

  render() {
    return <li className="todo">
      <div className="view">
        <input type="checkbox"
               className="toggle" />
        <label htmlFor="todo">
          {this.props.text}
        </label>
        <button className="destroy"></button>
      </div>
      <TextInput /> // We add the TextInput component
    </li>
  }
};

The TextInput component can be written as follows:

src/components/TextInput.jsx

import React from 'react';

export default class TextInput extends React.Component {
  render() {
    return <input className="edit"
                  autoFocus={true}
                  type="text" />
  }
};

The benefits of “pure” components: the PureRenderMixin

Note: here is the relevant commit in the companion repository.

Apart for allowing a functional programming style, the fact that our UI is purely dependant on props allows us to use the PureRenderMixin for a performance boost, as per the React docs:

“If your React component’s render function is “pure” (in other words, it renders the same result given the same props and state), you can use this mixin for a performance boost in some cases.”

It is quite easy to add it to our child components, as shown in the React documentation (we will see in part two that the TodoApp component has some extra role that prevents the use of the PureRenderMixin):

npm install --save react-addons-pure-render-mixin@0.14.7

src/components/TodoList.jsx

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin'
import TodoItem from './TodoItem';

export default class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }
  render() {
    // ...
  }
};

src/components/TodoItem.jsx

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin'
import TextInput from './TextInput';

export default class TodoItem extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }
  render() {
    // ...
  }
};

src/components/TextInput.jsx

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin'

export default class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }
  render() {
    // ...
  }
};

Handling user actions in the list components

Okay, so now we have our UI set up for the list components. However, none of what we have written yet takes into account user actions and how the app responds to them.

The power of props

In React, the props object is passed by settings attributes when we instantiate a container. For example, if we instantiate a TodoItem element this way:

<TodoItem text={'Text of the item'} />

Then we can access, in the TodoItem component, the this.props.text variable:

// in TodoItem.jsx
console.log(this.props.text);
// outputs 'Text of the item'

The Redux architecture makes an intensive use of props. The basic principle is that (nearly) every element’s state should be residing only in its props. To put it another way: for the same set of props, two instances of an element should output the exact same result. As we saw before, the entire state of the app is contained in the state tree: this means that the state tree, passed down to components as props, will entirely and predictably determine the app’s visual output.

The TodoList component

Note: here is the relevant commit in the companion repository.

In this section and the following, we are going to follow a test-first approach.

In order to help up test our components, the React library provides the TestUtils addons that provide, among others, the following methods:

  • renderIntoDocument, that renders a component into a detached DOM node;
  • scryRenderedDOMComponentsWithTag, that finds all instances of components in the DOM with the provided tag (like li, input…);
  • scryRenderedDOMComponentsWithClass, that finds all instances of components in the DOM with the provided class;
  • Simulate, that simulates user actions (a click, a key press, text inputs…)

The TestUtils addon is not included in the react package, so we have to install it separately:

npm install --save-dev react-addons-test-utils@0.14.7

Our first test will ensure that the TodoList components displays all the active items in the list it has been given if the filter props has been set to active:

test/components/TodoList_spec.jsx

import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TodoList from '../../src/components/TodoList';
import {expect} from 'chai';
import {List, Map} from 'immutable';

const {renderIntoDocument,
       scryRenderedDOMComponentsWithTag} = TestUtils;

describe('TodoList', () => {
  it('renders a list with only the active items if the filter is active', () => {
    const todos = List.of(
      Map({id: 1, text: 'React', status: 'active'}),
      Map({id: 2, text: 'Redux', status: 'active'}),
      Map({id: 3, text: 'Immutable', status: 'completed'})
    );
    const filter = 'active';
    const component = renderIntoDocument(
      <TodoList filter={filter} todos={todos} />
    );
    const items = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(items.length).to.equal(2);
    expect(items[0].textContent).to.contain('React');
    expect(items[1].textContent).to.contain('Redux');
  });
});

We can see that our test is failing: instead of the two active items we want to have displayed, there are three. That is perfectly normal, as we haven’t yet wrote the logic to actually filter the items:

src/components/TodoList.jsx

// ...
export default class TodoList extends React.Component {
  // Filters the items according to their status
  getItems() {
    if (this.props.todos) {
      return this.props.todos.filter(
        (item) => item.get('status') === this.props.filter
      );
    }
    return [];
  }
  render() {
    return <section className="main">
      <ul className="todo-list">
        // Only the filtered items are displayed
        {this.getItems().map(item =>
          <TodoItem key={item.get('text')}
                    text={item.get('text')} />
        )}
      </ul>
    </section>
  }
};

Our first test passes! Let’s not stop there and add the tests for the filters all and completed:

test/components/TodoList_spec.js

// ...
describe('TodoList', () => {
  // ...
  it('renders a list with only completed items if the filter is completed', () => {
    const todos = List.of(
      Map({id: 1, text: 'React', status: 'active'}),
      Map({id: 2, text: 'Redux', status: 'active'}),
      Map({id: 3, text: 'Immutable', status: 'completed'})
    );
    const filter = 'completed';
    const component = renderIntoDocument(
      <TodoList filter={filter} todos={todos} />
    );
    const items = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(items.length).to.equal(1);
    expect(items[0].textContent).to.contain('Immutable');
  });

  it('renders a list with all the items', () => {
    const todos = List.of(
      Map({id: 1, text: 'React', status: 'active'}),
      Map({id: 2, text: 'Redux', status: 'active'}),
      Map({id: 3, text: 'Immutable', status: 'completed'})
    );
    const filter = 'all';
    const component = renderIntoDocument(
      <TodoList filter={filter} todos={todos} />
    );
    const items = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(items.length).to.equal(3);
    expect(items[0].textContent).to.contain('React');
    expect(items[1].textContent).to.contain('Redux');
    expect(items[2].textContent).to.contain('Immutable');
  });
});

The third test is failing, as the logic for the all filter is sligthly different – let’s update the component logic:

src/components/TodoList.jsx

// ...
export default React.Component {
  // Filters the items according to their status
  getItems() {
    if (this.props.todos) {
      return this.props.todos.filter(
        (item) => this.props.filter === 'all' || item.get('status') === this.props.filter
      );
    }
    return [];
  }
  // ...
});

At this time, we know that the items that are displayed on the app are filtered by the filter property. Indeed, if we look at the app in the browser, we see that no items are displayed as we haven’t yet set it:

src/index.jsx

// ...
const todos = List.of(
  Map({id: 1, text: 'React', status: 'active', editing: false}),
  Map({id: 2, text: 'Redux', status: 'active', editing: false}),
  Map({id: 3, text: 'Immutable', status: 'completed', editing: false})
);

const filter = 'all';

require('../node_modules/todomvc-app-css/index.css')

ReactDOM.render(
  <TodoApp todos={todos} filter = {filter}/>,
  document.getElementById('app')
);

src/components/TodoApp.jsx

// ...
export default class TodoApp extends React.Component {
  render() {
    return <div>
      <section className="todoapp">
        // We pass the filter props down to the TodoList component
        <TodoList todos={this.props.todos} filter={this.props.filter}/>
      </section>
    </div>
  }
};

Our items have now reappeared, and are filtered with the filter constant we have declared in the index.jsx file.

The TodoItem component

Note: here is the relevant commit in the companion repository.

Now, let’s take care of the TodoItem component. First of all, we want to make sure that the TodoItem component indeed renders an item. We also want to test the as yet unimplemented feature that when an item is completed, it is stricken-through:

test/components/TodoItem_spec.js

import React from 'react';
import TestUtils from 'react-addons-test-utils';
import TodoItem from '../../src/components/TodoItem';
import {expect} from 'chai';

const {renderIntoDocument,
       scryRenderedDOMComponentsWithTag} = TestUtils;

describe('TodoItem', () => {
  it('renders an item', () => {
    const text = 'React';
    const component = renderIntoDocument(
      <TodoItem text={text} />
    );
    const todo = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(todo.length).to.equal(1);
    expect(todo[0].textContent).to.contain('React');
  });

  it('strikes through the item if it is completed', () => {
    const text = 'React';
    const component = renderIntoDocument(
      <TodoItem text={text} isCompleted={true}/>
    );
    const todo = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(todo[0].classList.contains('completed')).to.equal(true);
  });
});

To make the second test pass, we should apply the class completed to the item if the status, which will be passed down as props, is set to completed. We will use the classnames package to manipulate our DOM classes when they get a little complicated:

npm install --save classnames

src/components/TodoItem.jsx

import React from 'react';
// We need to import the classNames object
import classNames from 'classnames';

import TextInput from './TextInput';

export default class TodoItem extends React.Component {
  render() {
    var itemClass = classNames({
      'todo': true,
      'completed': this.props.isCompleted
    });
    return <li className={itemClass}>
      // ...
    </li>
  }
};

An item should also have a particular look when it is being edited, a fact that is encapsulated by the isEditing prop:

test/components/TodoItem_spec.js

// ...
describe('TodoItem', () => {
  //...

  it('should look different when editing', () => {
    const text = 'React';
    const component = renderIntoDocument(
      <TodoItem text={text} isEditing={true}/>
    );
    const todo = scryRenderedDOMComponentsWithTag(component, 'li');

    expect(todo[0].classList.contains('editing')).to.equal(true);
  });
});

In order to make the test pass, we only need to update the itemClass object:

src/components/TodoItem.jsx

// ...
export default class TodoItem extends React.Component {
  render() {
    var itemClass = classNames({
      'todo': true,
      'completed': this.props.isCompleted,
      'editing': this.props.isEditing
    });
    return <li className={itemClass}>
      // ...
    </li>
  }
};

The checkbox at the left of the item should be ckecked if the item is completed:

test/components/TodoItem_spec.js

// ...
describe('TodoItem', () => {
  //...

  it('should be checked if the item is completed', () => {
    const text = 'React';
    const text2 = 'Redux';
    const component = renderIntoDocument(
      <TodoItem text={text} isCompleted={true}/>,
      <TodoItem text={text2} isCompleted={false}/>
    );
    const input = scryRenderedDOMComponentsWithTag(component, 'input');
    expect(input[0].checked).to.equal(true);
    expect(input[1].checked).to.equal(false);
  });
});

React has a method to set the state of a checkbox input: defaultChecked.

src/components/TodoItem.jsx

// ...
export default class TodoItem extends React.Component {
  render() {
    // ...
    return <li className={itemClass}>
      <div className="view">
        <input type="checkbox"
               className="toggle"
               defaultChecked={this.props.isCompleted}/>
        // ...
      </div>
    </li>
  }
};

We also have to pass down the isCompleted and isEditing props down from the TodoList component:

src/components/TodoList.jsx

// ...
export default class TodoList extends React.Component {
  // ...
  // This function checks whether an item is completed
  isCompleted(item) {
    return item.get('status') === 'completed';
  }
  render() {
    return <section className="main">
      <ul className="todo-list">
        {this.getItems().map(item =>
          <TodoItem key={item.get('text')}
                    text={item.get('text')}
                    // We pass down the info on completion and editing
                    isCompleted={this.isCompleted(item)}
                    isEditing={item.get('editing')} />
        )}
      </ul>
    </section>
  }
};

For now, we are able to reflect the state of our app in the components: for
example, a completed item will be stricken. However, a webapp also handles user
actions, such as clicking on a button. In the Redux model, this is also
processed using props, and more specifically by passing callbacks as props.
By doing so, we separate once again the UI from the logic of the app: the
component need not knowing what particular action will derive from a click –
only that the click will trigger something.

To illustrate this principle, we are going to test that if the user clicks on
the delete button (the red cross), the deleteItem function is called:

Note: here is the relevant commit in the companion repository.

test/components/TodoItem_spec.jsx

// ...
// The Simulate helper allows us to simulate a user clicking
const {renderIntoDocument,
       scryRenderedDOMComponentsWithTag,
       Simulate} = TestUtils;

describe('TodoItem', () => {
  // ...
  it('invokes callback when the delete button is clicked', () => {
    const text = 'React';
    var deleted = false;
    // We define a mock deleteItem function
    const deleteItem = () => deleted = true;
    const component = renderIntoDocument(
      <TodoItem text={text} deleteItem={deleteItem}/>
    );
    const buttons = scryRenderedDOMComponentsWithTag(component, 'button');
    Simulate.click(buttons[0]);

    // We verify that the deleteItem function has been called
    expect(deleted).to.equal(true);
  });
});

To make this test pass, we must declare an onClick handler on the delete
button that will call the deleteItem function passed in the props:

src/components/TodoItem.jsx

// ...
export default class TodoItem extends React.Component {
  render() {
    // ...
    return <li className={itemClass}>
      <div className="view">
        // ...
        // The onClick handler will call the deleteItem function given in the props
        <button className="destroy"
                onClick={() => this.props.deleteItem(this.props.id)}></button>
      </div>
      <TextInput />
    </li>
  }
};

It is important to note that the actual logic for deleting the item has not been
implemented yet: that will be the role of Redux.

On the same model, we can test and imlement the following features:

  • A click on the checkbox should call the toggleComplete callback
  • A double click on the item label should call the editItem callback

test/components/TodoItem_spec.js

// ...
describe('TodoItem', () => {
  // ...
  it('invokes callback when checkbox is clicked', () => {
    const text = 'React';
    var isChecked = false;
    const toggleComplete = () => isChecked = true;
    const component = renderIntoDocument(
      <TodoItem text={text} toggleComplete={toggleComplete}/>
    );
    const checkboxes = scryRenderedDOMComponentsWithTag(component, 'input');
    Simulate.click(checkboxes[0]);

    expect(isChecked).to.equal(true);
  });

  it('calls a callback when text is double clicked', () => {
    var text = 'React';
    const editItem = () => text = 'Redux';
    const component = renderIntoDocument(
      <TodoItem text={text} editItem={editItem}/>
    );
    const label = component.refs.text
    Simulate.doubleClick(label);

    expect(text).to.equal('Redux');
  });
});

src/components/TodoItem.jsx

// ...
render() {
  // ...
  return <li className={itemClass}>
    <div className="view">
      // We add an onClick handler on the checkbox
      <input type="checkbox"
             className="toggle"
             defaultChecked={this.props.isCompleted}
             onClick={() => this.props.toggleComplete(this.props.id)}/>
      // We add a ref attribute to the label to facilitate the testing
      // The onDoubleClick handler is unsurprisingly called on double clicks
      <label htmlFor="todo"
             ref="text"
             onDoubleClick={() => this.props.editItem(this.props.id)}>
        {this.props.text}
      </label>
      <button className="destroy"
              onClick={() => this.props.deleteItem(this.props.id)}></button>
    </div>
    <TextInput />
  </li>

We also have to pass down the editItem, deleteItem and toggleComplete functions as props down from the TodoList component:

src/components/TodoList.jsx

// ...
export default class TodoList extends React.Component {
  // ...
  render() {
      return <section className="main">
        <ul className="todo-list">
          {this.getItems().map(item =>
            <TodoItem key={item.get('text')}
                      text={item.get('text')}
                      isCompleted={this.isCompleted(item)}
                      isEditing={item.get('editing')}
                      // We pass down the callback functions
                      toggleComplete={this.props.toggleComplete}
                      deleteItem={this.props.deleteItem}
                      editItem={this.props.editItem}/>
          )}
        </ul>
      </section>
    }
};

Setting up the other components

Now that you are a little more familiar with the process, and in order to keep
the length of this article in reasonable constraints I invite you to have a look
at the companion repository for the code responsible for the TextInput (relevant commit), TodoHeader (relevant commit) and TodoTools and Footer (relevant commit) components. If you have any question about those components please leave a comment here, or an issue on the repo!

You may notice that some functions, such as editItem, toggleComplete and the like, have not yet been defined. They will be in the next part of this tutorial as Redux actions, so do not worry yet if your console start throwing some errors about those.

Wrap up

In this article we have paved the way for our very first React, Redux and
Immutable web app. Our UI is modular, fully tested and ready to be wired up with
the actual app logic. How will that work? How can these seemingly dumb
components, that don’t even know what they are supposed to do, empower us to
write an app that can travel back in time?

Part two of the tutorial is available here!


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • seantimm

    Thanks for the thorough write-up, Nicolas! My one piece of critical feedback would be that it is a bit misleading to suggest that this example is using ES2015 when the class definitions are using the old syntax, and the only ES2015 syntax I noticed was the use of the import / export and const statements. It would be great to see this updated to reflect full ES2015 markup: https://facebook.github.io/react/docs/reusable-components.html#es6-classes (and probably https://facebook.github.io/react/docs/pure-render-mixin.html)

  • Leo Cavalcante

    Can’t you use a simple function instead of PureRenderMixin?

  • Nicolas Goutay

    Thanks for your feedback! While digging a bit on the topic I read this highly interesting thread in the React Github repo. This quote is particularly revealing:

    For complex components, defining shouldComponentUpdate (eg. pure render) will generally exceed the performance benefits of stateless components.

    So for now, a PureRenderMixin would still perform faster than a pure functional component – although I agree with you that the latter is cleaner to read and write.

  • Nicolas Goutay

    Thank you for taking the time to leave a comment! Unfortunately ES2015 classes in React do not support the PureRenderMixin (or any other mixin, for that matter):

    Unfortunately ES6 launched without any mixin support. Therefore, there is no support for mixins when you use React with ES6 classes. Instead, we’re working on making it easier to support such use cases without resorting to mixins.

    As explained in another comment, the PureRenderMixin still has performance benefits over pure functional components, which is why I chose to use it.

  • seantimm

    I assumed that was why you were doing that, and I thought the same thing, but then I saw the second example on this page: https://facebook.github.io/react/docs/pure-render-mixin.html

  • Leo Cavalcante

    Interesting, thanks!

  • Nicolas Goutay

    Thanks for pointing that out! I updated the article to use ES2015 classes instead :)

  • Sad Unicorn

    Hello, Nicolas. I was doing your tutorial step-by-step by coping code from the article and found some typos. They were easy to find and fix but anyway: 1. In the first version of TodoApp there is an extra ‘)’ in the end. 2. When you add TextInput component to TextItem the TextItem.jsx exports TextInput Class instead of TextItem. 3. Missing comma in itemClass after ‘completed’ property. 4. Extra ‘.’ in TodoList ‘toggleComplete={this.props.toggleComplete.}’. I really like TDD aproach, JS tests looks great and intuitive, but I get an error when trying to run them. I think it is the same error as in opened issue on GitHub. Waiting for the part two where all cool stuff is expected :)

  • Nicolas Goutay

    Hello ! Thanks for the feedback, I’ve updated the article accordingly. As for your problem, I’ve responded on the GitHub issue for a possible fix, although I have no Windows machine available to test it.

  • Sad Unicorn

    Thanks a lot! It worked for me

  • Neil Williams

    I am now ready for your next article, this article really pulled a lot of things together for me about Redux (even though you are not there yet, the way you have structured you components makes a lot of sense and the test are great). There were a couple of glitches in this tutorial, mainly due to changing your react structure to class based, but looking at your final code on github resolved that. It not the actual code that is important, its the thinking behind why you do things that I find interesting.

    Can’t wait for the next one.

  • seehou

    I ran webpack-dev-server and got ERR_CONNECTION_REFUSED until I add the port like this

    devServer: {
    contentBase: ‘./dist’,
    hot: true, // Activate hot loading
    port: 8080
    },

    I wonder is this step necessary?

  • Aaron Spiegel

    Great article. I spent some time converting the referenced TextInput commit to a React.Component. Then saw that you had done that is the “master” branch. In any case, I added a few specs to make sure I knew what I was doing: https://gist.github.com/spiegela/542656b3cbe3ea7128068a2647977d55

  • Altair

    in last TodoItem.jsx you wrote
    defaultChecked={this.isCompleted()}
    which is not correct according to yourself github code:
    defaultChecked={this.props.isCompleted}

  • Nicolas Goutay

    Thanks ! I’ve corrected the mistake.

  • mildy_interested

    `npm install -save-dev jsdom` should have an extra hypen before save: `npm install –save-dev jsdom`

    I just started. This looks promising. The heavier tutorial you say inspired this looks well received. I’m hoping to do this one then potentially that one, or sources using a different server/middleware to fit my needs. Thanks.

  • Nicolas Goutay

    Thanks for the feedback, I’ve corrected the typo! I hope you enjoy reading the tutorial as much as I enjoyed writing it. The tutorial by @teropa is my favorite resource out here for learning Redux, and one of the best written walkthrough I’ve come across :)

  • React Tutorial

    This blog post was featured in The React Newsletter #26. http://eepurl.com/bXo8gz

  • Olajide Aderibigbe

    Great write up Nicolas. Succinctly explained. Loved it and recommended it to a colleague. Do you have any write up for Redux (Reducer and actions) tests too?

  • bloppy

    I read through this, and it’s very well done by the way, but when I got to the test for if it was adding the completed class, the test fails because todo[0].classList is undefined, and same for the other tests that reference .classList. I tried cloning your repo and running the tests on it unmodified, using Node 6.2.1 and 4.0.0 and I still get the same error. Any idea what the problem is?

  • Nicolas Goutay

    Oh, that’s weird, sorry to hear that. I tried cloning a fresh repo and running the tests, which passed, using Node 4.0.0 & 4.2.5. You could maybe remove your node components and install them again, using rm -rf node_components/ and npm install? Could you please also open an issue on https://github.com/phacks/redux-todomvc/issues so we can try and see what went wrong?

  • jungible

    Is it better to init default state at the top container (in example filter) or in the subcomponent constructor?

  • having relative imports is one of the baddest practices of imports in es2015/16 ( as in `import TodoList from ‘../../src/components/TodoList’;`)

    hopefully you can patch your webpack config with a more evolved `resolve` node :

    “`
    const SRC = path.join(__dirname, ‘src/’);

    resolve: {
    root : [SRC, __dirname + ‘/node_modules/’],
    alias: {
    ‘actions’ : path.join(SRC, ‘scripts/actions’),
    ‘components’: path.join(SRC, ‘scripts/components’),
    ‘stores’ : path.join(SRC, ‘scripts/stores’)
    }
    },
    “`

  • Tu AN Huynh Van

    Thanks so much for the tutorial. This is really suitable for my current experience with web development and the choices of the tools for the entire stack are really good for beginners for me to start. I was previously struggling with react, so redux became even harder for me to pick up. This first part is really self-explanatory and easy to follow. I have finished this part and will continue to part 2. :)

  • Mike

    How are you running the tests? In your tutorial we write the test and it just says…
    ..We can see that our test is failing:..
    How?

  • Mike

    NM, I forgot about the tests watcher…