React/Redux TodoMVC, Part 3: State

Last time we introduced React; this time we’ll introduce Redux!

It’s time to add application state. How about the TODO items? Seems like a decent place to start.

Before we can add our first piece of application state, though, there’s some boilerplate we need to write. It’s time to introduce a lot of new concepts, including Redux.

Please be patient with this step. It seems like a lot to get started, but trust me, once it’s all in place, adding to it is really simple and easy!

Actions and State

Actions and state are kind of related. Once you have both actions and state, then you can add actions or state individually. But when you’re first starting out, you really have to define actions and state.

So, in order to define our list of TODO items (as state), we also have to define an action to go along with it. An “Add TODO” action sounds like it would be a great fit!

Actions

Actions are just plain old objects. Our actions are going to follow the Flux Standard Action model, which means they’ll have a type property (generally a string, since that makes debugging and serialization easier) and (for non-error actions) a payload property. There are a couple other properties, but those are for more advanced scenarios; this simple app will only need type and payload.

Let’s define our “Add TODO” action! Well, it’ll need a type first off. In real-world projects, it’s useful to define all the action types in a single file. So, that’s what I’ll do here (in actionTypes.jsx):

export default {
    ADD_TODO: 'ADD_TODO'
};

Next, the payload. The data that travels as arguments of the ADD_TODO action is the TODO item itself (a string). So the payload can simply be a string in this case.

Action Creators

Now that we have a good understanding of the structure of our action, we can implement the action creator. There’s a helper library called redux-actions that minimizes the repetitive code. With that, we can define our ADD_TODO action (in actions/todoActions.jsx):

import ActionTypes from '../actionTypes';
import { createAction } from 'redux-actions';

export default {
    add: createAction(ActionTypes.ADD_TODO)
};

This is a bit dense… To clarify, we’re creating an object TodoActions that has a method add on it. This method takes a single parameter, which is the value of the action’s payload.

So, the line:

    add: createAction(ActionTypes.ADD_TODO)

is really the same as this:

    add: createAction(ActionTypes.ADD_TODO, todoItem => todoItem)

which is the same as this:

    add: todoItem => ({ type: ActionTypes.ADD_TODO, payload: todoItem })

In Redux, an Action Creator is just a function that creates an action.

State

The next step is to define some application state (our collection of todos), and define how the ADD_TODO action affects that state. Presumably, it should just add the new item to our collection. The TODOs in our collection will need a couple properties: the actual TODO text (which I’ll call text), and a flag indicating whether the TODO is completed (which I’ll call completed).

This is where it gets a bit wild.

Let’s define the reducer first, using the react-actions helper method handleActions (in reducer.jsx):

import { handleActions } from 'redux-actions';
import ActionTypes from './actionTypes';

export default handleActions({
    [ActionTypes.ADD_TODO]: (state, action) => [...state, { completed: false, text: action.payload }]
}, []);

OK, so let’s break this down. I’m calling handleActions with two parameters: an object containing the actual reducer method, and an empty array []. That second parameter is just the initial state of our todo collection - it’s easier to have it be an empty collection rather than undefined.

Now, let’s look at the object. I’m using the ES2015 computed property key syntax to define a function named ADD_TODO, which is our actual reducer.

Our reducer takes two parameters: the existing state and the incoming action, and returns the new state (that is, what the state becomes after applying the action).

Important note: A reducer should never do anything else! It’s a mistake to have a reducer do any I/O, or modify the existing state in-place. It should only return the new state.

For the ADD_TODO action, we just need to add a new TODO to our collection, setting completed to false and text to the TODO text (which, for the ADD_TODO action, is the action’s payload):

(state, action) => [...state, { completed: false, text: action.payload }]

I’m using the ES2015 array spread operator to easily create a new array of TODOs that contains all the elements of the old array of TODOs, plus the new TODO that I create from the action.

There are a few competing patterns for writing reducers. ImmutableJS is one popular option for representing state, in spite of its serialization difficulties. However, in my projects so far, I just use regular JS objects, along with a helper plugin that double-checks my immutability in dev builds. I find this approach is sufficient for me.

The Store

So, we’ve got some basic application state, and we’ve got an action, and we’ve got a reducer that applies that action to our state. Good so far, but there’s a couple more pieces to hook up still.

First off, we need to tell Redux to actually create our store. Recall from my drawing of how Redux works, that it will create the store using our reducer (the shaded parts are mostly done for us):

In Redux, our store is a singleton, so I’m going to just expose it as a global object from store.jsx:

import { createStore } from 'redux';
import reducers from './reducers';

export default createStore(reducers);

Quite literally, I’m taking our reducer from reducers.jsx and passing it to Redux’s createStore method. Very straightforward.

You might wonder why I bothered putting this in its own file. That will become clear in the next post, when I add debugging helpers in here.

The Views

The final piece to hook up is to provide our store to our view components. Right now, our view components don’t actually use the application state (that is, they’re still just displaying the boilerplate HTML, not our actual collection of TODO items). However, they will start displaying the application state soon enough!

We provide the store to our views using Redux’s Provider. It looks like this (in main.jsx):

import { Provider } from 'react-redux';
import store from './store';

.. // function Main() hasn't changed

window.onload = () => render(<Provider store={store}><Main/></Provider>, document.getElementById('app'));

That’s it for now! We give the Provider element our store, and it provides a way for our view components to get at it.

Now we have an action (with a type and a creator), a reducer to apply that action to our state, a singleton store containing the application state, and the provider that allows our views to read the application state.

The main thing missing, of course, is something to actually trigger the action. We’ll add that in soon, though!

Source code at this revision - Live site at this revision (ignore the “startup flicker”; that’s just due to the way it’s hosted)

Most current source code - Most current live site