Benefits of Using Immutable.js With React & Redux Apps

Have you ever struggled with complex and unreadable redux reducers? If yes, this article will show you how Immutable.js can help you keep reducers easy and clean. It fits perfectly with the redux & react application, so you might try to use it in your app.

Immutable.js is a library that supports an immutable data structure. It means that once created data cannot be changed. It makes maintaining immutable data structures easier and more efficient. Immutable.js supports data structure like: List, Map, Set and also structures that are not implemented in .js by default but can be very useful: OrderedMap, OrderedSet and Record.

Methods such as push, unshift, slice in .js are based on reference and mutate the object directly. In the case of Immutable.js, there are no methods that change the object directly, a new object is always returned.

How Using Immutable.js Is Supposed to Help in Redux Applications?

Before using Immutable.js, the biggest issue with the Redux library often comes to returning a new object, which is nested in another object. In this case, using the Object.assign and spread operator syntax is not readable and may increase app complexity.

Some may suggest keeping your reducer's state as flat as possible. That could be right, but sometimes, even if your state is flat, you would have to set something in a nested object. So, if you also struggle because of that, the immutable library comes to make your life easier.

How does it look in practice?

Let’s start by showing some examples of how the code looks like with and without using Immutable.js in a reducer. In most of the cases in reducers, you will use method .set, which takes two arguments; the first one is a key which you would like to change and the second one is a new value. For setting nested properties, you can use method .setIn, which instead of a key as the first argument takes a key path as an array. Worth noting here is that if the key does not exist, a new one will be created. Thanks to this, you don't have to make conditions to handle it.

Here is a very simple reducer without Immutable.js:


export const initialState ={
  loaded: false,
  disabled: false
};
 
export default function bookReducer(state = initialState, { type, payload }) {
  switch (type) {
    case ActionTypes.setLoadedState:
      return {
        ...state,
        loaded: payload
      }
  }

  return state;
}

This is the simplest reducer you can imagine, let's see what it looks like with immutable.js:


export const initialState = from.js({
  loaded: false,
  disabled: false
});
 
export default function bookReducer(state = initialState, { type, payload }) {
  switch (type) {
    case ActionTypes.setLoadedState:
      return state.set('loaded', payload)
  }
 
  return state;
}

Here, there is no big difference because the reducer is very simple, but we already can see a small improvement, code becomes more readable.

The second example without immutable.js:


export const initialState = {
  students: {},
  selectedStudent: null
};
 
export default function studentReducer(state = initialState, { type, payload }) {
  switch (type) {
    case ActionTypes.setStudentStatus:
      return {
        ...state,
        students: {
          ...state.students,
           [payload.studentId]: {
             ...state.students[payload.studentId],
             status: payload.status
           }
        }
      }
  }
 
  return state;
}

With Immutable.js:


export const initialState = {
  students: {},
  selectedStudent: null
};
 
export default function studentReducer(state = initialState, { type, payload }) {
  switch (type) {
    case ActionTypes.setStudentStatus:
      return state.setIn(['students', payload.studentId, 'status'], payload.status)
  }
 
  return state;
}

In the example above, we can see a huge difference between using Immutable.js and not using the tool:

  • The code is much shorter (10 lines to just 1 line).
  • With the Immutable.js you can easily see at first glance what data in reducer has changed.
  • Without the Immutable.js, it’s not that literal and obvious what’s changed.

In these examples, we provide only 2 methods of using Immutable.js - .set and .setIn, but there are numerous use cases, not only to set values. Actually, Immutable objects have the same methods which native .js has and a lot more which can speed up your development.

We also recommend checking the .update and .updateIn methods in the documentation, because, in reducers, they can be invaluable in more complex cases.

Other Benefits of Using Immutable.js

The main benefits of this library are easy and simple to maintain reducers. Besides this, we also get other advantages:

  • The library provides data structures that are not natively implemented in .js, but it makes your life easier (e.g., Ordered Map, Record).
  • Immutable.js offers numerous ways to simplify your work, for example sortBy, groupBy, reverse, max, maxBy, flatten and many more. You can even stop using the lodash library, as most of the methods from lodash have their equivalents in immutable.js. It is easier to use as we can use chaining by default.
  • Immutable.js does a lot of things under the hood, which improves performance. Immutable data structures usually consume a lot of RAM, because this approach requires creating new copies of objects constantly. Among other things, Immutable.js optimizes this, by sharing the state cleverly.
  • Empty collections are always equal, even if they were created separately. Look at the example below:

Compared to native .js:

There Is Always the Other Side of the Coin, What Are the Cons?

Expensive converting to regular JavaScript

To convert Immutable collection to regular .js, you have to use .to.js() on an Immutable Collection. This method is very expensive when it comes to performance and always returns a new reference of an object even if nothing has been changed in the object. It affects PureComponent and React.memo, because these components would detect something has been changed, but actually, nothing has changed.

In most of the cases, you should avoid using to.js() and pass to components Immutable collections. However, sometimes you will have to use to.js, e.g. if you use an external library that requires props.

If you are developing generic components that will be used in other projects, you should avoid using an Immutable Collection in them, because it would force you to use Immutable in all projects that use these components.
There is no destructing operator

If you like getting properties using a destructing operator like this:


const { age, status } = student;

You won’t be happy, because, in Immutable.js, it is impossible to do. The get property from an immutable collection you have to use method .get or getIn, but I think it should not be a bit deal.

Debugging

Immutable collections are difficult to read in the browser console. Fortunately, you can easily solve this problem by using the Immutable..js Object Formatter browser plugin, but it is not available in all browsers.

The above comparison shows what it looks like without and with the plugin. As you can see, the log is completely unreadable without the plugin.

Conclusion

Accordingly to our experiences, the immutable.js library is worth trying out in React applications with Redux applications. Thanks to immutable.js, your application will be more efficient, easier to develop, maintain and more resistant to errors. Because, as you’ve seen above in a comparison of reducers. It's definitely easier to make a mistake without using Immutable.js. In the long term project, you should definitely consider it.

Share the story