React, Redux and Immutable.js: Ingredients for Efficient Web Applications

View all articles

React, Redux and Immutable.js are currently among the most popular JavaScript libraries and are rapidly becoming developers’ first choice when it comes to front-end development. In the few React/Redux projects that I have worked on, I realised that a lot of developers getting started with React do not fully understand React and how to write efficient code to utilise its full potential.

React, Redux and Immutable.js: Ingredients for Performant Web Applications

In this article we will build a simple app using React, Redux and Immutable.js, and identify some of the most common misuses of React and ways to avoid them.

Data Reference Problem

React is all about performance. It was built from the ground up to be extremely performant, only re-rendering minimal parts of DOM to satisfy new data changes. Any React app should mostly consist of small simple (or stateless function) components. They are simple to reason about and most of them can have shouldComponentUpdate function returning false.

shouldComponentUpdate(nextProps, nextState) {
   return false;
}

Performance wise, the most important component lifecycle function is shouldComponentUpdate and if possible it should always return false. This ensures that this component will never re-render (except the initial render) effectively making the React app feel extremely fast.

When that is not the case, our goal is to make a cheap equality check of old props/state vs new props/state and skip re-rendering if the data is unchanged.

Let’s take a step back for a second and review how JavaScript performs equality checks for different data types.

Equality check for primitive data types like boolean, string and integer is very simple since they are always compared by their actual value:

1 === 1
’string’ === ’string’
true === true

On the other hand, equality check for complex types like objects, arrays and_functions_ is completely different. Two objects are the same if they have the same reference (pointing to the same object in memory).

const obj1 = { prop: ’someValue’ };
const obj2 = { prop: ’someValue’ };
console.log(obj1 === obj2);   // false

Even though obj1 and obj2 appear to be the same, their reference is different. Since they are different, comparing them naively within the shouldComponentUpdate function will cause our component re-render needlessly.

The important thing to note is that the data coming from Redux reducers, if not set up correctly, will always be served with different reference which will cause component to re-render every time.

This is a core problem in our quest to avoid component re-rendering.

Handling References

Let’s take an example in which we have deeply nested objects and we want to compare it to its previous version. We could recursively loop through nested object props and compare each one, but obviously that would be extremely expensive and is out of the question.

That leaves us with only one solution, and that is to check the reference, but new problems emerge quickly:

  • Preserving the reference if nothing has changed
  • Changing reference if any of the nested object/array prop values changed

This is not an easy task if we want to do it in a nice, clean, and performance optimised way. Facebook realised this problem a long time ago and called Immutable.js to the rescue.

import { Map } from ‘immutable’;

//  transform object into immutable map
let obj1 = Map({ prop: ’someValue’ });  
const obj2 = obj1;
console.log(obj1 === obj2);  // true

obj1 = obj1.set(‘prop’, ’someValue’);  // set same old value
console.log(obj1 === obj2);  // true | does not break reference because nothing has changed 

obj1 = obj1.set(‘prop’, ’someNewValue’);   // set new value
console.log(obj1 === obj2);  // false | breaks reference 

None of the Immutable.js functions perform direct mutation on the given data. Instead, data is cloned internally, mutated and if there were any changes new reference is returned. Otherwise it returns the initial reference. New reference must be set explicitly, like obj1 = obj1.set(...);.

React, Redux and Immutable.js

The best way to demonstrate the power of these libraries is to build a simple app. And what can be simpler than a todo app?

For brevity, in this article, we will only walk through the parts of the app that are critical to these concepts. The entire source code of the app code can be found on GitHub.

When the app is started you will notice that calls to console.log are conveniently placed in key areas to clearly show the amount of DOM re-render, which is minimal.

Like any other todo app, we want to show a list of todo items. When the user clicks on a todo item we will mark it as completed. Also we need a small input field on top to add new todos and on the bottom 3 filters which will allow the user to toggle between:

  • All
  • Completed
  • Active

Redux Reducer

All data in Redux application lives inside a single store object and we can look at the reducers as just a convenient way of splitting the store into smaller pieces that are easier to reason about. Since reducer is also a function, it too can be split into even smaller parts.

Our reducer will consist of 2 small parts:

  • todoList
  • activeFilter
// reducers/todos.js
import * as types from 'constants/ActionTypes';
// we can look at List/Map as immutable representation of JS Array/Object
import { List, Map } from 'immutable';  
import { combineReducers } from 'redux';  

function todoList(state = List(), action) {  // default state is empty List()
  switch (action.type) {
  case types.ADD_TODO:
    return state.push(Map({   // Every switch/case must always return either immutable 
      id: action.id,          //  or primitive (like in activeFilter) state data  
      text: action.text,      //  We let Immutable decide if data has changed or not
      isCompleted: false,
    }));

  // other cases...

  default:
    return state;
  }
}


function activeFilter(state = 'all', action) {
  switch (action.type) {
  case types.CHANGE_FILTER:
    return action.filter;  // This is primitive data so there’s no need to worry

  default:
    return state;
  }
}

// combineReducers combines reducers into a single object
// it lets us create any number or combination of reducers to fit our case
export default combineReducers({
  activeFilter,
  todoList,
});

Connecting with Redux

Now that we have set up a Redux reducer with Immutable.js data, let’s connect it with React component to pass the data in.

// components/App.js
import { connect } from 'react-redux';
// ….component code
const mapStateToProps = state => ({ 
    activeFilter: state.todos.activeFilter,
    todoList: state.todos.todoList,
});

export default connect(mapStateToProps)(App);

In a perfect world, connect should be performed only on top level route components, extracting the data in mapStateToProps and the rest is basic React passing props to children. On large scale applications it tends to get hard to keep track of all the connections so we want to keep them to a minimum.

It is very important to note that state.todos is a regular JavaScript object returned from Redux combineReducers function (todos being the name of the reducer), but state.todos.todoList is an Immutable List and it is critical that it stays in such a form until it passes shouldComponentUpdate check.

Avoiding Component Re-render

Before we dig deeper, it is important to understand what type of data must be served to the component:

  • Primitive types of any kind
  • Object/array only in immutable form

Having these types of data allows us to shallowly compare the props that come into React components.

Next example shows how to diff the props in the simplest way possible:

$ npm install react-pure-render
import shallowEqual from 'react-pure-render/shallowEqual';

shouldComponentUpdate(nextProps, nextState) {
  return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}

Function shallowEqual will check the props/state diff only 1 level deep. It works extremely fast and is in perfect synergy with our immutable data. Having to write this shouldComponentUpdate in every component would be very inconvenient, but fortunately there is a simple solution.

Extract shouldComponentUpdate into a special separate component:

// components/PureComponent.js
import React from 'react';
import shallowEqual from 'react-pure-render/shallowEqual';

export default class PureComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
  }
}

Then just extend any component in which this shouldComponentUpdate logic is desired:

// components/Todo.js
export default class Todo extends PureComponent  {
  // Component code
}

This is a very clean and efficient way of avoiding component re-render in most cases, and later if the app gets more complex and suddenly requires custom solution it can be changed easily.

There is a slight problem when using PureComponent while passing functions as props. Since React, with ES6 class, does not automatically bind this to functions we have to do it manually. We can achieve this by doing one of the following:

  • use ES6 arrow function binding: <Component onClick={() => this.handleClick()} />
  • use bind: <Component onClick={this.handleClick.bind(this)} />

Both approaches will cause Component to re-render because different reference has been passed to onClick every time.

To work around this problem we can pre-bind functions in constructor method like so:

  constructor() {
    super();
    this.handleClick = this.handleClick.bind(this);
  }
 // Then simply pass the function
  render() {
    return <Component onClick={this.handleClick} />
  }

If you find yourself pre-binding multiple functions most of the time, we can export and reuse small helper function:

// utils/bind-functions.js
export default function bindFunctions(functions) {
  functions.forEach(f => this[f] = this[f].bind(this));
}
// some component
  constructor() {
    super();
    bindFunctions.call(this, ['handleClick']);   // Second argument is array of function names
  }

If none of the solutions work for you, you can always write shouldComponentUpdate conditions manually.

Handling Immutable Data Inside a Component

With the current immutable data setup, re-render has been avoided and we are left with immutable data inside a component’s props. There are number of ways to use this immutable data, but the most common mistake is to convert the data right away into plain JS using immutable toJS function.

Using toJS to deeply convert immutable data into plain JS negates the whole purpose of avoiding re-rendering because as expected, it is very slow and as such should be avoided. So how do we handle immutable data?

It needs to be used as is, that’s why Immutable API provides a wide variety of functions, map and get being most commonly used inside React component. todoList data structure coming from the Redux Reducer is an array of objects in immutable form, each object representing a single todo item:

[{
  id: 1,
  text: 'todo1',
  isCompleted: false,
}, {
  id: 2,
  text: 'todo2',
  isCompleted: false,
}]

Immutable.js API is very similar to regular JavaScript, so we would use todoList like any other array of objects. Map function proves best in most cases.

Inside a map callback we get todo, which is an object still in immutable form and we can safely pass it in Todo component.

// components/TodoList.js
render() {
   return (
      // ….
            {todoList.map(todo => {
              return (
                <Todo key={todo.get('id')}
                    todo={todo}/>
              );
            })}
      //  ….
    );
}

If you plan on performing multiple chained iterations over immutable data like:

myMap.filter(somePred).sort(someComp)

… then it is very important to first convert it into Seq using toSeq and after iterations turn it back to desired form like:

myMap.toSeq().filter(somePred).sort(someComp).toOrderedMap()

Since Immutable.js never directly mutates given data, it always needs to make another copy of it, performing multiple iterations like this can be very expensive. Seq is lazy immutable sequence of data, meaning it will perform as few operations as possible to do its task while skipping creation of intermediate copies. Seq was built to be used this way.

Inside Todo component use get or getIn to get the props.

Simple enough right?

Well, what I realized is that a lot of the time it can get very unreadable having a large number of get() and especially getIn(). So I decided to find a sweet-spot between performance and readability and after some simple experiments I found out that Immutable.js toObject and toArray functions work very well.

These functions shallowly convert (1 level deep) Immutable.js objects/arrays into plain JavaScript objects/arrays. If we have any data deeply nested inside, they will remain in immutable form ready to be passed to components children and that is exactly what we need.

It is slower than get() just by a negligible margin, but looks a lot cleaner:

// components/Todo.js
render() {
  const { id, text, isCompleted } = this.props.todo.toObject();
  // …..
}

Let’s See It All in Action

In case you have not cloned the code from GitHub yet, now is a great time to do it:

git clone https://github.com/rogic89/ToDo-react-redux-immutable.git
cd ToDo-react-redux-immutable

Starting the server is as simple (make sure Node.js and NPM are installed) as this:

npm install
npm start

Navigate to http://localhost:3000 in your web browser. With the developer console open, watch the logs as you add a few todo items, mark them as done and change the filter:

  • Add 5 todo items
  • Change filter from ‘All’ to ‘Active’ and then back to ‘All’
    • No todo re-render, just filter change
  • Mark 2 todo items as completed
    • Two todos were re-rendered, but only one at a time
  • Change filter from ‘All’ to ‘Active’ and then back to ‘All’
    • Only 2 completed todo items were mounted/unmounted
    • Active ones were not re-rendered
  • Delete a single todo item from the middle of the list
    • Only the todo item removed was affected, others were not re-rendered

Wrap Up

The synergy of React, Redux and Immutable.js, when used right, offer some elegant solutions to many performance issues that are often encountered in large web applications. Immutable.js allows us to detect changes in JavaScript objects/arrays without resorting to the inefficiencies of deep equality checks, which in turn allows React to avoid expensive re-render operations when they are not required.

I hope you liked the article and find it useful in your future React endeavours.

About the author

Ivan Rogic, Croatia
member since August 15, 2015
Ivan first started coding back in 2007 at the beginning of his college education, and he became really passionate about it. He likes learning new technologies and staying on top of his game all the time. During his early employment, he learned a lot about the importance of communication between team members and how to be a great team player. [click to continue...]
Hiring? Meet the Top 10 Freelance React.js Developers for Hire in August 2016

Comments

Michel H.
Nice post! When I started out with React I though of JavaScript as a pure functional language. But what I didn't know was, a lack of lazy evaluation and built-in immutable data prevents JS from becoming a functional language. This is because most interpreters are call-by-name and not call-by-need. It corrects the flaws of underscore.js, namely that operations of different data structures were forced on JavaScript arrays and objects, mixing the concept of data types, and losing immutability. Long story short, only React made me realize how important Immutable.js really is! So weird....
Guillaume Claret
Thanks for the post! Talking about components with: ```` shouldComponentUpdate(nextProps, nextState) { return false; } ``` this can only work when there are no props and no state, right?
Kabir Baidya
Great Post. Pretty well explained
Ladi
Brilliant article! Just one question. Is it really necessary to use Immutable.js? According to official documentation reducer function in Redux should always emit a new state object using Object.assign(), so why is there need for such a huge library?
Ivan Rogić
It is not necessary to use Immutable.js, but then you have to be really careful and know exactly how you calculate and return reducer state. Not to mention it looks unreadable and hard to maintain with all pre/post slicing of the arrays --> http://redux.js.org/docs/basics/Reducers.html and these are just simple examples. Also since ImmutableJS has versatile API, in most cases it removes the need for helper libraries like loadash.
Alexis Mangin
Why does blog articles talking about Redux are always demonstrating "simple" examples? I would love to see hard-core example using Redux, with login and API calls!
jackyon
Hi, I saw the code: case types.ADD_TODO: return state.push(Map({ id: action.id, text: action.text, isCompleted: false, })); and just little curious, I have tested without using "Map", and it is still return the immutable data. so what's the reason to use "Map" method there? plz advise, thx!
Jason
It was double spread, this part, [...base, ...args] FYI now supported by node v6 :)
eagspoo
I'm constantly surprised by people promoting immutable.js. In principle using immutable data structures with redux/react is great but immutable is a terrible example of this idea and there are far far better javascript alternatives (updeep being my favorite).
Raphael
Hello, I have a problem npm start works but when opening in browser I have an error "ERROR in ./src/init.js Module build failed: TypeError: Path must be a string. Received undefined"?
sushill kumar
Awesome blog !!! Just one question you mentioned old version of react in package.json does these matters for application speed?
Ivan Rogić
Thx! Well its better to have newest version of React, they are updating it quite frequently, but for this example it's not important, since it was meant to show how to avoid unnecessary re-renders of DOM in React app.
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design 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
Ivan Rogic
JavaScript Developer
Ivan first started coding back in 2007 at the beginning of his college education, and he became really passionate about it. He likes learning new technologies and staying on top of his game all the time. During his early employment, he learned a lot about the importance of communication between team members and how to be a great team player.