You may not need Immutable.js

So you finally got a job you always dreamed of – as a React developer. There is a new project starting so you and your team have to setup everything. That’s a wonderful occasion to use some trendy tech and do the things right this time. You decide to use battle tested react-boilerplate as a foundation of a frontend app. You clone the repo and start checking out an example app.

Actions, sagas, reducers… Wait, what? What is that strange way of setting properties on an object? Why you can’t use simple dot notation to access those properties? You start digging through dependencies and find package called Immutable.js. It is responsible for all that fuss. Why react-boilerplate author decided to use it? Do you have to use it? Why you can’t write your reducers with plain old JavaScript? And what the heck is that immutability thing at first place?

What is immutability?

Immutability is a concept best known from functional languages like Clojure or Lisp. It says that data can’t be mutated no matter what. But does it mean that once we create our data we cannot change it? No, it just means, that each time we want to change anything in our data we have to create complete copy with that change taken into account. We treat all data structures as working in read-only mode. Let’s see an example:

const arr = [1, 2, 3];

// we want to change second element in an array to 4,
// but we also want our data to be immutable, what can we do?

const changedArr = [...arr.slice(0, 1), 4, ...arr.slice(2)];

// or with object

const obj = {
  foo: 'bar',
};

const changedObj = {
  ...foo,
  bar: 'baz',
};

It doesn’t look very nice, right? Why this is better than writing arr[1] = 4? Let’s look closer and see why immutability matters and why it is so important in React/Redux world.

Why it matters?

Treating every data structure in our code as immutable gives us high level of trust and confidence, that our code will behave exactly as we want. In JavaScript world we are using mostly references to values, not values themselves. It means, that every code that has access to reference can change the value it references:

const foo = {
  bar: 'baz',
};

someMysteriousFunction(foo); // do I really trust this function, that it will not mutate my object?
console.log(foo.bar); // can you tell for sure that it will log ‘baz’?

In the above example someMysteriousFunction could possibly mutate foo properties. It means we can’t trust this function, we can’t trust any code that has access to reference of that object. Unless it is immutable.

Immutability is particularly important in context of Redux, because it enables Redux to work correctly and be fast. Redux (and react-redux) uses shallow equality comparison to make decisions about updates of state and components. To make this possible, Redux requires reducers to be pure functions with no side effects. It means that you have to return new state from reducer, not mutated current state. This contract enables Redux to be fast and reliable. It also enables features like time travel debugging to work.

What is wrong with Immutable.js?

Now we know that immutability is a valuable concept and embracing it in our code will make it better. So why I think that using Immutable.js is not a good idea in most cases? Here I have to make a little digresion and ask you to don’t get me wrong – I’m not trying to say that Immutable.js is a piece of garbage and you should avoid it. It is a superb piece of software written by very clever developers. I think that you should alway choose the best tool for particular task, and Immutable.js is one of those tools, but by design it has some characteristics that I personally don’t like. Here they are:

  • a lot of added boilerplate and no possibility to use standard syntax while working with objects and arrays – we have to use API
  • most libs are not compatible with it, so we have to make extensive use of fromJS and toJS helpers
  • if you eventually change your mind and want to get rid of it, you will have a hard time doing this, because it’s own syntax is present in whole codebase
  • performance boost that it promises is rarely really observed in vast majority of apps using it
  • it makes development noticeably slower

In my humble opinion the balance of profits and losses of using Immutable.js is not positive in most cases.

What can we do about it?

So, do we have to grit our teeth and use Immutable.js, or are there any alternatives? Luckily there are! I will briefly present some of them, that I find very appealing and easy to work with.

Object spread operator

Like we have seen in previous example, we can use fairly new but standard object spread operator. It works similarly to Object.assign static method, but is less verbose. It is available out of the box in all modern JavaScript runtimes and it also can be transpiled to older ES5 code using tools like Babel. Our reducer may look like this:

const reducer = (state, action) => ({
  ...state,
  myChangingProp: action.payload,
});

As we see it is very easy and elegant syntax. And we are not using any import – it works out of the box! The problem with it starts when we have more complex object tree and we want to change property which is deeply nested:

const reducer = (state, action) => ({
  ...state,
  nestedObj: {
    ...state.nestedObj,
    anotherNestedObj: {
      ...state.nestedObj.anotherNestedObj,
      finallyAPropToChange: action.payload,
    },
  },
});

It starts to look cluttered. Solution will be to keep your state as flat as possible, what you should do anyway when working with Redux. If you don’t want to (or have to) work with deeply nested object you can try some libs available through npm. I will briefly present to you two of them.

Immer

Let’s start with Immer. I will show you the same example with deeply nested object, but this time using Immer:

import produce from 'immer';

const reducer = (state, action) => produce(state, draft => {
  draft.nestedObj.anotherNestedObj.finallyAPropToChange = action.payload;
});

It looks way cleaner! Finally we could use standard and familiar JS syntax to make changes in a state and still benefit from immutability. At first glance it seems that we are mutating our data and it’s in some sense true. We are mutating draft state, which is a proxy to our true state. This proxy records all changes and then creates new state with these changes included – that is what produce function is doing.

Moreover, we can extract information from state using selectors and pass it down to components without them knowing that we are using Immer in reducers (which is not the case with Immutable.js). This is a huge advantage – it doesn’t tie components code to yet another lib and makes them easier to reuse. If you want to know more about inner workings of Immer please check out their Github page: https://github.com/mweststrate/immer

seamless-immutable

We are moving on to the next – seamless-immutable. Again, let’s first see the code:

import Immutable from 'seamless-immutable';

const initialState = Immutable({});

const reducer = (state, action) =>
  state.setIn(['nestedObj', 'anotherNestedObj', 'finallyAPropToChange'], action.payload);

As you can see, state is not a plain JavaSript object anymore, it is wrapped with Immutable factory. We also have to use API to make a change. Acknowledge, that this setIn method will not mutate state, it will return new state, that’s why we could use this as a return value of reducer. It looks a lot like code that uses Immutable.js, so why I think it’s better?

Although we have to use API to “mutate” data, we could use standard dot or bracket notation to access it, because seamless-immutable is backwards-compatible with plain JS objects and arrays. This is the same situation as with Immer – only reducers know that we are using seamless-immutable. Selectors, components, and other code “thinks”, that it is dealing with standard objects. Of course, there are some edge cases, when you will have to call toMutable to get real object, but they are rare. You can read more about seamless-immutable here: https://github.com/rtfeldman/seamless-immutable

Conclusion

Surprisingly or not, you may not need Immutable.js to enforce immutability of your data structures. I use words „may not” instead of „do not”, because I think that Immutable.js is just a tool and you should treat it like one and use it wisely. You can and should use it, if your use case requires it, but keep in mind that it is not de facto standard and there are many alternatives. I showed you a few of them which I use and find easy and pleasant to work with. Let me know in the comments if you agree or disagree with me – constructive discussion is what makes us all more open minded and conscious about what we know and what we don’t know yet.

Sources:

https://medium.com/@Noitidart/reasons-i-dislike-immutable-js-7a11246fd31a
https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310
https://redux.js.org/faq/immutable-data#how-redux-uses-shallow-checking

Our Blog

Blog posts

How to become a better software developer: a business perspective

If you’re trying to become a better software developer, you probably already have some ideas how to approach this challenge….

How to reduce customer service costs by as much as 30%?

There are many ways to optimize costs or increase the efficiency of your customer service department. If you are a…

Innovation ROI – how to measure it

Buzzwords Innovation, Digitalisation, Blockchain, AI – we all hear these words every day, yet they still hold some kind of…