In an ever growing ecosystem of rich and complicated JavaScript applications, there’s more state to be managed than ever before: the current user, the list of posts loaded, etc.

Any set of data that needs a history of events can be considered stateful. Managing state can be hard and error prone, but working with immutable data (rather than mutable) and certain supporting technologies- namely Redux, for the purposes of this article- can help significantly.

Immutable data has restrictions, namely that it can’t be changed once it’s created, but it also has many benefits, particularly in reference versus value equality, which can greatly speed up applications that rely on frequently comparing data (checking if something needs to update, for example).

Using immutable states allows us to write code that can quickly tell if the state has changed, without needing to do a recursive comparison on the data, which is usually much, much faster.

This article will cover the practical applications of Redux when managing state through action creators, pure functions, composed reducers, impure actions with Redux-saga and Redux Thunk and, finally, use of Redux with React. That said, there are a lot of alternatives to Redux, such as MobX, Relay, and Flux based libraries.

Redux components.

Why Redux?

The key aspect that separates Redux from most other state containers such as MobX, Relay, and most other Flux based implementations is that Redux has a single state that can only be modified via “actions” (plain JavaScript objects), which are dispatched to the Redux store. Most other data stores have the state contained in React components themselves, allow you to have multiple stores and/or use mutable state.

This in turn causes the store’s reducer, a pure function that operates on immutable data, to execute and potentially update the state. This process enforces unidirectional data flow, which is easier to understand and more deterministic.

The Redux Flow.

Since Redux reducers are pure functions operating on immutable data, they always produce the same output given the same input, making them easy to test. Here’s an example of a reducer:

import Immutable from 'seamless-immutable'

const initialState = Immutable([]) // create immutable array via seamless-immutable

/**
 * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state.
 */
function addUserReducer(state = initialState, action) {
    if (action.type === 'USERS_ADD') {
      return state.concat(action.payload)
    }
    
    return state // note that a reducer MUST return a value
}

// somewhere else...

store.dispatch({ type: 'USERS_ADD', payload: user }) // dispatch an action that causes the reducer to execute and add the user

Dealing in pure functions allows Redux to easily support many use cases that are generally not easily done with mutative state, such as:

  • Time travel (Going back in time to a previous state)
  • Logging (Track every single action to figure out what caused a mutation in the store)
  • Collaborative environments (Such as GoogleDocs, where actions are plain JavaScript objects and can be serialized, sent over the wire, and replayed on another machine)
  • Easy bug reporting (Just send the list of actions dispatched, and replay them to get the exact same state)
  • Optimized rendering (At least in frameworks that render virtual DOM as a function of state, such as React: due to immutability, you can easily tell if something has changed by comparing references, as opposed to recursively comparing the objects)
  • Easily test your reducers, as pure functions can easily be unit tested

Action Creators

Redux’s action creators help in keeping code clean and testable. Remember that “actions” in Redux are nothing more than plain JavaScript objects describing a mutation that should occur. That being said, writing out the same objects over and over again is repetitive and error prone.

An action creator in Redux is simply a helper function that returns a plain JavaScript object describing a mutation. This helps reduce repetitive code, and keeps all your actions in one place:

    export function usersFetched(users) {
      return {
        type: 'USERS_FETCHED',
        payload: users,
      }
    }
    
    export function usersFetchFailed(err) {
      return {
        type: 'USERS_FETCH_FAILED',
        payload: err,
      }
    }
    
    // reducer somewhere else...
    const initialState = Immutable([]) // create immutable array via seamless-immutable
    
    /**
     * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state.
     */
    function usersFetchedReducer(state = initialState, action) {
        if (action.type === 'USERS_FETCHED') {
          return Immutable(action.payload)
        }
        
        return state // note that a reducer MUST return a value
    }

Using Redux with Immutable Libraries

While the very nature of reducers and actions make them easy to test, without an immutability helper library, there’s nothing protecting you from mutating objects, meaning the tests for all your reducers have to be particularly robust.

Consider the following code example of a problem you’ll run into without a library to protect you:

const initialState = []

function addUserReducer(state = initialState, action) {
    if (action.type === 'USERS_ADD') {
      state.push(action.payload) // NOTE: mutating action!!
      return state
    }
    
    return state // note that a reducer MUST return a value
}

In this code example, time travel will be broken as the previous state will now be the same as the current state, pure components may potentially not update (or re-render) as the reference to the state has not changed even though the data it contains has changed, and mutations are a lot harder to reason through.

Without an immutability library, we lose all the benefits that Redux provides. It’s therefore highly recommended to use an immutability helper library, such as immutable.js or seamless-immutable, especially when working in a large team with multiple hands touching code.

Regardless of which library you use, Redux will behave the same. Let’s compare the pros and cons of both so that you’re able to pick whichever one is best suited for your use case:

Immutable.js

Immutable.js is a library, built by Facebook, with a more functional style take on data structures, such as Maps, Lists, Sets, and Sequences. Its library of immutable persistent data structures perform the least amount of copying possible in between different states.

Pros:

  • Structural sharing
  • More efficient at updates
  • More memory efficient
  • Has a suite of helper methods to manage updates

Cons:

  • Does not work seamlessly with existing JS libraries (i.e lodash, ramda)
  • Requires conversion to and from (toJS / fromJS), especially during hydration / dehydration and rendering

Seamless-immutable

Seamless-immutable is a library for immutable data that is backwards compatible all the way to ES5.

It’s based on ES5 property definition functions, such as defineProperty(..) to disable mutations on objects. As such, it is fully compatible with existing libraries like lodash and Ramda. It can also be disabled in production builds, providing a potentially significant performance gain.

Pros:

  • Works seamlessly with existing JS libraries (i.e lodash, ramda)
  • No extra code needed to support conversion
  • Checks can be disabled in production builds, increasing performance

Cons:

  • No structural sharing - objects / arrays are shallow-copied, makes it slower for large data sets
  • Not as memory efficient

Redux and Multiple Reducers

Another useful feature of Redux is the ability to compose reducers together.This allows you to create much more complicated applications, and in an application of any appreciable size, you will inevitably have multiple types of state (current user, the list of posts loaded, etc). Redux supports (and encourages) this use case by naturally providing the function combineReducers:

import { combineReducers } from 'redux'
import currentUserReducer from './currentUserReducer'
import postsListReducer from './postsListReducer'

export default combineReducers({
  currentUser: currentUserReducer,
  postsList: postsListReducer,
})

With the above code, you can have a component that relies on the currentUser and another component that relies on the postsList. This also improves performance as any single component will only be subscribing to whatever branch(es) of the tree concerns them.

Impure Actions in Redux

By default, you can only dispatch plain JavaScript objects to Redux. With middleware, however, Redux can support impure actions such as getting the current time, performing a network request, writing a file to disk, and so on.

‘Middleware’ is the term used for functions that can intercept actions being dispatched. Once intercepted, it can do things like transform the action or dispatch an asynchronous action, much like middleware in other frameworks (such as Express.js).

Two very common middleware libraries are Redux Thunk and Redux-saga. Redux Thunk is written in an imperative style, while Redux-saga is written in a functional style. Let’s compare both.

Redux Thunk

Redux Thunk supports impure actions within Redux by using thunks, functions that return other chain-able functions. To use Redux-Thunk, you must first mount the Redux Thunk middleware to the store:

    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    
    const store = createStore(
      myRootReducer,
      applyMiddleware(thunk), // here, we apply the thunk middleware to R
    )

Now we can perform impure actions (such as performing an API call) by dispatching a thunk to the Redux store:

    store.dispatch(
      dispatch => {
        return api.fetchUsers()
          .then(users  => dispatch(usersFetched(users)) // usersFetched is a function that returns a plain JavaScript object (Action)
          .catch(err => dispatch(usersFetchError(err)) // same with usersFetchError
      }
    )

It’s important to note that using thunks can make your code hard to test and makes it harder to reason through code flow.

Redux-saga

Redux-saga supports impure actions through an ES6 (ES2015) feature called generators and a library of functional / pure helpers. The great thing about generators is that they can be resumed and paused, and their API contract makes them extremely easy to test.

Let’s see how we can improve readability and testability of the previous thunk method using sagas!

First, let’s mount the Redux-saga middleware to our store:

    import { createStore, applyMiddleware } from 'redux'
    import createSagaMiddleware from 'redux-saga'
    
    import rootReducer from './rootReducer'
    import rootSaga from './rootSaga'
    
    // create the saga middleware
    const sagaMiddleware = createSagaMiddleware()
    
    // mount the middleware to the store
    const store = createStore(
      rootReducer,
      applyMiddleware(sagaMiddleware),
    )
    
    // run our saga!
    sagaMiddleware.run(rootSaga)

Note that the run(..) function must be called with the saga for it to begin executing.

Now let’s create our saga:

    import { call, put, takeEvery } from 'redux-saga/effects' // these are saga effects we'll use
    
    export function *fetchUsers(action) {
      try {
        const users = yield call(api.fetchUsers)
        yield put(usersFetched(users))
      } catch (err) {
        yield put(usersFetchFailed(err))
      }
    }
    
    export default function *rootSaga() {
      yield takeEvery('USERS_FETCH', fetchUsers)
    }

We defined two generator functions, one that fetches the users list and the rootSaga. Notice that we didn’t call api.fetchUsers directly but instead yielded it in a call object. This is because Redux-saga intercepts the call object and executes the function contained within to create a pure environment (as far as your generators are concerned).

rootSaga yields a single call to a function called takeEvery, which takes every action dispatched with a type of USERS_FETCH and calls the fetchUsers saga with the action it took. As we can see, this creates a very predictable side effect model for Redux, which makes it easy to test!

Testing Sagas

Let’s see how generators make our sagas easy to test. We’ll be using mocha in this part to run our unit tests and chai for assertions.

Because sagas yield plain JavaScript objects and are run within a generator, we can easily test that they perform the right behavior without any mocks at all! Keep in mind that call , take , put, etc are just plain JavaScript objects that are intercepted by the Redux-saga middleware.

    import { take, call } from 'redux-saga/effects'
    import { expect } from 'chai'
    import { rootSaga, fetchUsers } from '../rootSaga'
    
    describe('saga unit test', () => {
      it('should take every USERS_FETCH action', () => {
        const gen = rootSaga() // create our generator iterable
        expect(gen.next().value).to.be.eql(take('USERS_FETCH')) // assert the yield block does have the expected value
        expect(gen.next().done).to.be.equal(false) // assert that the generator loops infinitely
      })
      
      it('should fetch the users if successful', () => {
        const gen = fetchUsers()
        expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded
        
        const users = [ user1, user2 ] // some mock response
        expect(gen.next(users).value).to.be.eql(put(usersFetched(users))
      })
      
      it('should fail if API fails', () => {
        const gen = fetchUsers()
        expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded
        
        const err = { message: 'authentication failed' } // some mock error
        expect(gen.throw(err).value).to.be.eql(put(usersFetchFailed(err))
      })
    })

Working with React

While Redux isn’t tied to any specific companion library, it works especially well with React.js since React components are pure functions that take a state as input and produce a virtual DOM as output.

React-Redux is a helper library for React and Redux that eliminates most of the hard work connecting the two. To most effectively use React-Redux, let’s go over the notion of presentational components and container components.

Presentational components describe how things should look visually, depending solely on their props to render; they invoke callbacks from props to dispatch actions. They’re written by hand, completely pure, and are not tied to state management systems like Redux.

Container components, on the other hand, describe how things should function, are aware of Redux, dispatch Redux actions directly to perform mutations and are generally generated by React-Redux. They are often paired with a presentational component, providing its props.

Presentational components and container components in Redux.

Let’s write a presentational component and connect it to Redux via React-Redux:

    const HelloWorld = ({ count, onButtonClicked }) => (
      <div>
        <span>Hello! You've clicked the button {count} times!</span>
        <button onClick={onButtonClicked}>Click me</button>
      </div>
    )
    
    HelloWorld.propTypes = {
      count: PropTypes.number.isRequired,
      onButtonClicked: PropTypes.func.isRequired,
    }

Note that this is a “dumb” component that completely relies on its props to function. This is great, because it makes the React component easy to test and easy to compose. Let’s look at how to connect this component to Redux now, but first let’s cover what a Higher Order Component is.

Higher Order Components

React-Redux provides a helper function called connect( .. ) that creates a higher order component from a “dumb” React component that is aware of Redux.

React emphasizes extensibility and re-usability through composition, which is when you wrap components in other components. Wrapping these components can change their behavior or add new functionality. Let’s see how we can create a higher order component out of our presentational component that is aware of Redux - a container component.

Here’s how you do it:

    import { connect } from 'react-redux'
    
    const mapStateToProps = state => { // state is the state of our store
      // return the props that we want to use for our component
      return {
        count: state.count,
      }
    }
    
    const mapDispatchToProps = dispatch => { // dispatch is our store dispatch function
      // return the props that we want to use for our component
      return {
        onButtonClicked: () => {
          dispatch({ type: 'BUTTON_CLICKED' })
        },
      }
    }
    
    // create our enhancer function
    const enhancer = connect(mapStateToProps, mapDispatchToProps)
    
    // wrap our "dumb" component with the enhancer
    const HelloWorldContainer = enhancer(HelloWorld)
    
    // and finally we export it
    export default HelloWorldContainer

Note that we defined two functions, mapStateToProps and mapDispatchToProps .

mapStateToProps is a pure function of (state: Object) that returns an object computed from the Redux state. This object will be merged with the props passed to the wrapped component. This is also known as a selector, since it selects parts of the Redux state to be merged into the component’s props.

mapDispatchToProps is also a pure function, but one of (dispatch: (Action) => void) that returns an object computed from the Redux dispatch function. This object will likewise be merged with the props passed to the wrapped component.

Now to use our container component we must use the Provider component in React-Redux to tell the container component what store to use:

    import { Provider } from 'react-redux'
    import { render } from 'react-dom'
    
    import store from './store' // where ever your Redux store resides
    import HelloWorld from './HelloWorld'
    
    render(
      (
        <Provider store={store}>
          <HelloWorld />
        </Provider>
      ), document.getElementById('container')
    )

The Provider component propagates the store down to any child components who subscribe to the Redux store, keeping everything in one place and reducing points of error or mutation!

Build Code Confidence With Redux

With this newfound knowledge of Redux, its numerous supporting libraries and its framework connection with React.js, you can easily limit the number of mutations in your application through state control. Strong state control, in turn, lets you you move faster and create a solid code base with more confidence.

About the author

David Xu, United States
member since September 14, 2016
David has taken several mobile apps from an idea to millions of users all over the world, as the Chief Architect of Castle Global, Inc and technical lead in other companies. He has been programming since the age 9 and has won medals in various competitive programming competitions, from the Australian Informatics Olympiad to ACM-ICPC. David has invaluable experience in all areas technical, from architecture and design to engineering and DevOps. [click to continue...]
Hiring? Meet the Top 10 Freelance JavaScript Developers for Hire in February 2017

Comments

Andrey Bogomazov
"No structural sharing - objects / arrays are shallow-copied, makes it slower for large data sets" Shallow copy is a structural sharing, did you mean it performs a deep copy?
Rakesh mohanta
Nice one! Worth Reading... Feeling more reactive.
Michel H.
I am working with Node frameworks (FeathersJS and Loopback) and redux is just such a great addition to the front end world. Do not want to work without it again!
Miguel Angel Salinas Gancedo
Why use React + Redux whe Angular offer everything? ... Thanks for your great tutorial.
David Xu
Structural sharing and shallow copying are 2 different things. Immutable.js is built with persistent data structures, which are data structures that remember every single state they've ever been in (https://en.wikipedia.org/wiki/Persistent_data_structure) whereas seamless-immutable is based on shallow copying the entire data structure. Persistent data structures are for the most part, a lot more memory and time efficient, especially when dealing with larger data sets.
Arif Azim
You need to use "ngrx/store" (RxJS powered state management for Angular2 apps, inspired by Redux) Check here for details : https://gist.github.com/btroncone/a6e4347326749f938510
Arif Azim
Thanks David for sharing Redux here. Angular 2 developers should refer "ngrx/store", RxJS powered state management. It operates on many of the same concepts: actions, reducers, and a single store. Check out the Lukas Ruebbelke’s great post on ngrx for an overview - http://onehungrymind.com/build-better-angular-2-application-redux-ngrx/
David Xu
You can also use Redux with Angular! https://github.com/angular-redux/ng-redux
rdenadai
Good article... i'm being using React with Redux (and also React-Redux and Redux-Thunk) and must say.. it great... it has it's drawbacks in some places, but in the overall is awesome. I think all, my newer projects i'll use them...
guigrpa
Thanks for the report! For completeness, you might want to consider the Timm (tiny immutability) library: https://github.com/guigrpa/timm - faster than seamless-immutable (benchmarks in the project page), and seamless (unlike immutable.js). See also this blog post: http://guigrpa.github.io/2016/06/16/painless-immutability/
JJ Dubray
you may want to study the theory behind application state mutation before concluding that "immutability" is the correct approach. I have heard several times people complaining about the "scalability" of an immutable approach, not to mention that React's programming model (mostly because of Redux's incomplete state management model) requires state to move in your view components or in Sagas, an obvious anti-pattern of Front-End architectures and a programming model that claims to be "single-state-tree" (https://twitter.com/AdamRackis/status/822162375101059072)
JJ Dubray
There is a cleaner alternative, using the SAM pattern and RxJS pub/sub: https://github.com/jdubray/sam-samples/tree/master/angular2-admin-master-2.0.0/src/app/pages/dashboard/todo
rowland
Awesome overview. Thank you!
Diogo Mello
Thanks for the article, really useful for people learning react-redux.
Szymon Przybylski
Great one! Thanks!
Julien Renaux
ngrx/store is good but it is not Redux so you do not have access to the same API. https://github.com/angular-redux/ng2-redux this project is a great bindings lib for Redux using Angular
Miguel
In my opinion, the main difference between react+redux+etc vs Angular is that with AngularJs you are forced to have to code in a given way, requested by the framework, while when using React + Redux + InmutableJs or whatever other combination, you are free to chose the mix that better combines for your project and/or personal mindset. I used Angular 1.x and I liked it. At some point I felt very bound to it and when trying to do certain things, I found myself fighting some characteristics of the framework. When using React + other tools, I chose the ones I like better for the purpose. As always, it is also a matter of taste... we are all different :)
Miguel
what would you say that it is the root of the problem? the concept of immutability or the implementations?
Miguel
Hello David, great article! Well written and explicative. I have a question about your tests for the sagas. It may well be a stuqpid question, because I am new to them... How is it that the call to `api.fetchUsers` not performed in here if the code is the same one? ``` it('should fetch the users if successful', () => { const gen = fetchUsers() expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded const users = [ user1, user2 ] // some mock response expect(gen.next(users).value).to.be.eql(put(usersFetched(users)) }) ``` I understand from your text that call(aFunctionHere) just returns a call object wrapping the function, as in the interpreter pattern used in some functional programming architectures. If that is correct, when and/or how would the api request be performed? I seem to be missing that point...
Poetro
You can have multiple stores with any Flux implementation, even with Redux, as you can createStore any number of times. Also if something is a Flux implementation, then you can only get data into it by dispatching actions. https://facebook.github.io/flux/docs/in-depth-overview.html#structure-and-data-flow Also React components can be pure functions, but they can be implementing the React.Component class, and they they can have their own state, which makes them statefull.
JJ Dubray
The root of the problem comes from the fact that for decades, we have been writing code based on three approximations: - actions can update the application state - assignments are equivalent to mutation - there is no need to define what a programming step is with precision (e.g. is "a = b + c" a step? or is it 3 steps: read b,c; compute b+c; assign result to a? These approximations no longer make sense in complex distributed systems. Redux/Elm aim at addressing these approximations in their own way. Unfortunately for Redux and Elm: - an action is a data structure (not a function) - there is one "big" assignment (due to immutability) - the "step" is a function This is possibly the worst way imaginable to fix these three approximations, though on paper it looks like you are addressing them somehow. In SAM actions propose updates to the model, model is mutable but controls 100% of the mutation, no rogue assignments, a step is clearly defined as action->model->state representation
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant Technologies
About the author
David Xu
Java Developer
David has taken several mobile apps from an idea to millions of users all over the world, as the Chief Architect of Castle Global, Inc and technical lead in other companies. He has been programming since the age 9 and has won medals in various competitive programming competitions, from the Australian Informatics Olympiad to ACM-ICPC. David has invaluable experience in all areas technical, from architecture and design to engineering and DevOps.