State management is one of the most difficult aspects of software development, making state mismanagement the source of nearly all bugs. This series explores Redux, a predictable state container for JavaScript applications. Redux not only aids in state management but also makes it straightforward to implement advanced features such as infinite undo/redo and live-editing time travel.

Although Redux came from the React community, it’s not tied to React. You can use Redux with or without a JavaScript framework such as React, Angular, Backbone, or Cycle.js. In this first installment, you start with Redux basics and learn how to use it as a stand-alone state container; then you segue into using Redux with React. The next few installments address more-advanced Redux scenarios, and in the final installment you learn about using Redux with Angular.

How Redux works

Redux is a simplified implementation of Facebook’s Flux architecture. (Redux is both an English word that means “returned” and a portmanteau of reducer + flux.) Flux is essentially Model-View-Controller (MVC) done right, but Flux introduces a fair amount of complexity. Redux lessens that complexity by borrowing the concept of reducers from Elm— a powerful reactive functional programming language based on immutable data structures and pure functions. Pure functions are functions that have no side effects, and Redux reducers are pure functions that compute application state.

Redux is founded on three principles:

  • Application state is stored in a single object.
  • Application state is immutable and can only be replaced outright through actions that describe state changes.
  • Reducers create the next state, given the current state and an action.

Application state is stored in a single object

Redux manages state through a single JavaScript object, known as a data store, where all of your application’s state is located. Centralizing state in a single object makes it easier to reason about application data when you’re reading code. Also, when all your data is in one place, your application is easier to debug and test. And storing application state in a single object makes it easier to persist the entire state of your application.

Application state is immutable

With Redux, you can’t modify application state. Instead, you replace the existing state with a new state. The new state is specified by actions, which are (also immutable) JavaScript objects that describe state changes.

Encapsulation of state changes in immutable objects has many advantages. One of those advantages, as you’ll see in this series, is the ability to implement endless undo and redo — in effect, a sort of time machine. Actions are also executed in a strict order, so no race conditions occur.

Reducers create a new state

Reducers are pure JavaScript functions that:

  • Create a new state, given the current state and an action
  • Centralize data mutations
  • Can act on all or part of the state
  • Can be combined and reused

Because they’re pure functions, reducers have no side effects — so they’re easy to read, test, and debug. And you can compose reducers, which makes it easy to implement simple reducers that are concerned with only a portion of the overall application state.

Because application state is immutable and because reducers are pure functions with no side effects, Redux is a predictable state container: Given any state and any action, you can predict the next state of the application with absolute certainty. That predictable element of Redux is the killer feature that opens the door to infinite undo/redo and live-editing time travel.

Next up are a couple of examples that illustrate the fundamentals of Redux.

Actions, reducers, and the store

In Figure 1, the code for a simple Redux example — a traffic light simulator — runs in the TextMate text editor. The TextMate-generated tooltip in the image shows the application’s output, which displays the app’s state (STOP, CAUTION, or GO).

Figure 1. Traffic light state with Redux
Screenshot of the traffic light code in the TextMate text editor

The code in Figure 1 begins by requiring Redux and creating the Redux store:


const Redux = require('redux');
const createStore = Redux.createStore;
const store = createStore(reducer);

You create your application’s store with the Redux.createStore() function. Every Redux application has exactly one store, and each store has exactly one reducer. You pass the reducer to the Redux.createStore() function.

The application’s state — in this case, a string whose value is either GO, STOP, or CAUTION— is created by the reducer() function. The reducer() function returns a new state given the current state and an action that describes a state change. Listing 1 shows the reducer for the traffic light application. Notice that the reducer() function returns GO when the state is undefined, as is initially the case.

Listing 1. The reducer for the traffic light app

const reducer = (state = 'GO', action) => {
  switch(action.type) {
     case 'GO':
        state = 'GO'
        break;

     case 'STOP':
        state = 'STOP'
        break;

     case 'CAUTION':
        state = 'CAUTION';
        break;
  }
  return state;
}

Next, the code defines three action objects, one for each state, as shown in Listing 2.

Listing 2. The actions for traffic light

const cautionAction = {
   type: 'CAUTION'
};

const goAction = {
   type: 'GO'
};

const stopAction = {
   type: 'STOP'
}

Finally, as shown in Listing 3, the application dispatches actions, obtaining a reference to the current state with the Redux store’s getState() method and printing the state’s value to the console.

Listing 3. Dispatching traffic actions

// Dispatch actions....................................................

store.dispatch(stopAction);
console.log('State: ' + store.getState());

store.dispatch(cautionAction);
console.log('State: ' + store.getState());

store.dispatch(goAction);
console.log('State: ' + store.getState());

Data flow

When you dispatch an action by using the Redux store.dispatch() function, Redux passes the action, in addition to the current state, to the application’s reducer. The reducer creates a new state and returns it to Redux. That flow is depicted in Figure 2.

Figure 2. Redux data flow
Diagram of the Redux data flow

In Figure 2, the action starts with the call to Redux.dispatch() in the diagram’s lower-right corner. The Redux dispatcher dispatches the action to the application’s reducer, passing the current state, along with the action, to the reducer. The reducer creates the new state and returns it to Redux. Finally, Redux notifies any view components that state has changed, causing the application to repaint. Redux notifies view components, such as React or Angular components, about state changes through framework-specific bindings.

The Redux API

The Redux overall API is simple, consisting of only five top-level functions (one of which, Redux.createStore(), you already know about):

  • Object createStore(reducer, initialState) – Create the Redux store.
  • Object combineReducers(reducers) – Combine several reducers into one.
  • Object compose(...functions) – Compose functions, left to right.
  • void applyMiddleware(...middlewares) – Apply Redux middleware.
  • Object bindActionCreators(actionCreators, dispatch) – Bind several action creators to the dispatch function.

Recall that Redux maintains a reference to a single reducer that’s in charge of computing the entire state of the application. But maintaining a single reducer function in complex applications can be unwieldy, especially when a team of developers is working together. The combineReducers() function, as its name implies, combines several reducer functions into one. You can then control the level of granularity of your reducer functions, and individual developers can work on those functions in isolation.

With the Redux applyMiddleware() function, you can extend Redux through middleware that intercepts dispatch calls. That handy facility makes it possible to implement all kinds of cross-cutting concerns, from logging to asynchronous actions.

As you’ll see in the next installment, Redux supports action creators— functions that create actions. The bindActionCreators() function binds action creators to the Redux dispatch() function, making it easy to compose action creators.

And as you’ve seen, the createStore() function creates the Redux store, given the application’s reducer. Notice that you can also pass the createStore() function the initial application state. Once you have a reference to the store, you can call the object’s methods:

  • Object getState() returns the current state of the application.
  • void dispatch(Objectaction) dispatches an action, triggering a state change.
  • replaceReducer(nextReducer) replaces the reducer for the state tree.
  • subscribe(Functioncallback) causes Redux to call the callback method for every dispatch.

That’s all there is to the Redux API.

Using Redux with React

Now that you know how to use Redux without a framework, you’re ready learn how to use it with React.

The traffic light application shown in Figure 3 uses the same state, reducer, and actions as the previous version — but in this case it’s a React application.

Figure 3. A traffic light
Screenshot of the React traffic light application

The application represents a traffic light in the United States. When the light is green, as is the case in the top picture in Figure 3, the only possible next state is Caution, so the Go and Stop buttons are disabled. When the light is yellow, the only possible next state is Stop. And when the light is red, the only possible next state is Go.

The code for the application’s reducer and its actions are unchanged, but each is now in its own file. Listing 4 shows the code for the application’s entry point.

Listing 4. The entry point (index.js)

import React from 'react';
import ReactDOM from 'react-dom';
import Redux, { createStore } from 'redux';

import { reducer } from './reducer';
import { App } from './app';

const store = createStore(reducer);

const render = () => (
  ReactDOM.render(<App store={store}/>,
    document.getElementById('example'))
)

store.subscribe(render);

render(); // initial render

The Listing 4 code uses ECMAScript 6 (ES6) imports to import React, ReactDOM, Redux, and the Redux createStore() function.

When the application starts, it creates a Redux store with the application’s reducer (shown in Listing 1). Next, the application subscribes to the Redux store with the store’s subscribe() method. When the state in the Redux store changes, Redux calls the render() function, which renders the App component. Notice that the code in Listing 4 sets the Redux store to an App property of the same name.

Listing 5 shows the App component.

Listing 5. The application (app.js)

import React, { Component } from 'react';
import { Stoplight } from './stoplight';
import { Buttons } from './buttons';

export class App extends Component {
  render() {
    return(
      <div>
        <Stoplight store={this.props.store} />
        <Buttons   store={this.props.store} />
      </div>
    )
  }
}

The App component in turn imports and renders two other components —Stoplight and the row of Buttons— and passes the Redux store to these child components.

Listing 6 shows the Stoplight component.

Listing 6. The stoplight (stoplight.js)

import React, { Component } from 'react';

const stopColor = (state) => {
  return state == 'STOP' ? 'red' : 'white';
}

const cautionColor = (state) => {
  return state == 'CAUTION' ? 'yellow' : 'white';
}

const goColor = (state) => {
  return state == 'GO' ? 'rgb(39,232,51)' : 'white';
}

export const Stoplight = ({
  store
}) => {
  const state = store.getState();

  return(
    <div style={{textAlign: 'center'}}>
      <svg height='170'>
        <circle cx='145' cy='60' r='15'
                fill={stopColor(state)}
                stroke='black'/>

        <circle cx='145' cy='100' r='15'
                fill={cautionColor(state)}
                stroke='black'/>

        <circle cx='145' cy='140' r='15'
                fill={goColor(state)}
                stroke='black'/>

      </svg>
    </div>
  )
}

Stoplight, which is a React stateless functional component, renders SVG circles whose color depends on the application’s state. The component obtains the state from the store’s getState() method and subsequently uses one of three helper methods to turn the state into a color.

By virtue of the call to the Redux store’s subscribe() method in Listing 4, React renders the traffic light when the Redux state changes. Those state changes are initiated by the application’s buttons. In Listing 7 — the code for the Buttons component — you can see how the buttons initiate state changes.

Listing 7. The buttons (buttons.js)

import React, { Component } from 'react';
import { goAction, cautionAction, stopAction } from './actions';

export const Buttons = ({
  store
}) => {
  const state = store.getState();

  return(
    <div style={{textAlign: 'center'}}>
      <button onClick={() => {store.dispatch(goAction)}}
              disabled={state == 'GO' || state == 'CAUTION'}
              style={{cursor: 'pointer'}}>
        Go
      </button>

      <button onClick={() => {store.dispatch(cautionAction)}}
              disabled={state == 'CAUTION' || state == 'STOP'}
              style={{cursor: 'pointer'}}>
        Caution
      </button>

      <button onClick={() => {store.dispatch(stopAction)}}
              disabled={state == 'STOP' || state == 'GO'}
              style={{cursor: 'pointer'}}>
        Stop
      </button>
    </div>

  )
}

When a button is rendered, it obtains a reference to the application state by calling the Redux store’s getState() method. The Buttons component then uses the state to configure the disabled state of the buttons.

When the user clicks a button, the button’s onClick() callback calls the Redux store’s dispatch() method, passing an appropriate action.

Stateless and connected components

The example application subscribes to the Redux store by registering a callback that Redux invokes whenever the store’s state changes. That callback renders the entire application. In effect, you connect the entire application to the Redux store. Connecting the entire application is sufficient for illustrative purposes, but it’s better to connect the Redux store to individual components via React’s forceUpdate() function.

Listing 8 shows a revised entry point for the traffic light application.

Listing 8. Revised entry point for the traffic light application (index.js)

'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import Redux, { createStore } from 'redux';

import { reducer } from './reducer';
import { App } from './app';

ReactDOM.render(<App store={createStore(reducer)}/>,
                document.getElementById('root'))

Compare Listing 8 to Listing 4, and you can see that the code no longer subscribes to the Redux store at the application level.

Listing 9 is a revised Stoplight component that implements componentWillMount(). That method subscribes to the Redux store and calls the component’s forceUpdate() method when the store changes.

Listing 9. Revised stoplight (stoplight.js)

'use strict';

import React, { Component } from 'react';

const stopColor = (store) => {
  return store.getState() == 'STOP' ? 'red' : 'white';
}

const cautionColor = (store) => {
  return store.getState() == 'CAUTION' ? 'yellow' : 'white';
}

const goColor = (store) => {
  return store.getState() == 'GO' ? 'rgb(39,232,51)' : 'white';
}

export class Stoplight extends Component {
  componentWillMount() {
    this.props.store.subscribe(() => {
      this.forceUpdate();
    });
  }

  render() {
    return(
      <div style={{textAlign: 'center'}}>
        <svg height='170'>
          <circle cx='145' cy='60' r='15'
                  fill={stopColor(this.props.store)}
                  stroke='black'/>

          <circle cx='145' cy='100' r='15'
                  fill={cautionColor(this.props.store)}
                  stroke='black'/>

          <circle cx='145' cy='140' r='15'
                  fill={goColor(this.props.store)}
                  stroke='black'/>
        </svg>
      </div>
    )
  }
}

Listing 10 shows a revised Buttons component that’s undergone a similar revision by implementing componentWillMount() to subscribe to the Redux store.

Listing 10. Revised buttons (buttons.js)

'use strict';

import React, { Component } from 'react';
import { goAction, cautionAction, stopAction } from './actions';

export class Buttons extends Component {
  componentWillMount() {
    this.props.store.subscribe(() => {
      this.forceUpdate();
    });
  }

  render() {
    const state = this.props.store.getState();

    return(
      <div style={{textAlign: 'center'}}>
        <button onClick={() => {this.props.store.dispatch(goAction)}}
                disabled={state == 'GO' || state == 'CAUTION'}
                style={{cursor: 'pointer'}}>
          Go
        </button>

        <button onClick={() => {this.props.store.dispatch(cautionAction)}}
                disabled={state == 'CAUTION' || state == 'STOP'}
                style={{cursor: 'pointer'}}>
          Caution
        </button>

        <button onClick={() => {this.props.store.dispatch(stopAction)}}
                disabled={state == 'STOP' || state == 'GO'}
                style={{cursor: 'pointer'}}>
          Stop
        </button>
      </div>
    )
  }
}

Compare the implementations of the Stoplight component in Listing 9 and the Buttons component Listing 10 to their original implementations ( Listing 6 and Listing 7). You can see that besides the addition of the componentWillMount() method, both components were changed from stateless functional components to classes. That’s because React stateless functional components don’t support React lifecycle methods.

All other things being equal, stateless components are preferable to components that maintain state, for the same reason that Redux is useful: Maintaining state is error prone and the source of most bugs. In the next installment, you’ll see how to revert the Stoplight and Buttons components to stateless components through the use of the Redux React bindings.

Conclusion to Part 1

Managing state is critical to any nontrivial application. Although state management might not be a glamorous topic, Redux — with 20,000 stars and 2,700 forks on GitHub at the time this article was written — is one of the most popular JavaScript libraries. The reason for its popularity is not only that Redux manages state but also that it does so predictably using immutable data and pure functions. That predictability makes it easy to implement a certain category of features, such as infinite undo/redo, that otherwise are typically arduous undertakings.

In this installment, you saw how to use Redux stand-alone and how to use it with React. The next installment goes deeper into using Redux with React and begins to explore advanced Redux features.