How not to Make your Projects Succeed with ImmutableJS! – Part 1

What is this article about?

When I first learned about ImmutableJS I was convinced it was a great addition to any project.
So I tried to make it a working standard at Theodo.
As we offer scrum web development teams for various projects going from POC for startups to years lasting ones for banks, we bootstrap applications all the time and we need to make a success of all of them!
So we put a strong emphasis on generalizing every finding and learning made on individual projects.
With this objective in mind we are determining which technical stack would be the best for our new projects.
Each developer contributes to this effort by improving the company’s technical stack whenever they makes a new learning.

When React was chosen as our component library we were just embarking on a hard journey that you may have experienced: making the dozens other decisions that come with a React app.

React then what?

One of those choices lead us to choose redux as our global state management lib.
We noticed that people were having troubles with Immutability in reducers, one of the three core principles of Redux

The first possibility to answer this purpose is defensive copying with ES6 spread operator { ...object }.

The second option we studied is Facebook’s ImmutableJS library.

immutable

Using ImmutableJS in your react-redux app means that you will no longer use JS data structures (objects, arrays) but immutable data structures (i.e. Map, List, …) and their methods like .set(), .get(). Using .set() on a Map, the Immutable equivalent of objects, returns a new Map with said modifications and does not alter previous Map.

In order to make an informed choice I observed the practices on projects that used either one of the two, gathered their issues and tried to find solutions for them.
This article is the result of this study and hopefully it will help you choose between those two!

In this first part I will compare those two options in the light of 3 criteria out of the 5 I studied: readability, maintainability and learning curve.
The second part of this study, coming soon, will explore performance and synergy with typing tools.

First Criterion, Readability: Immutable Wins!

If your state is nested and you use spread operators to achieve immutability, it can quickly become unreadable:

function reducer(state = defaultState, action) {
  switch (action.type) {
    case 'SET_HEROES_TO_GROUP':
      return {
        ...state,
        guilds: {
          ...state.guilds,
          [action.payload.guildId]: {
            ...state.guilds[action.payload.guildId],
            groups: {
              ...state.guilds[action.payload.guildId].groups,
              [action.payload.groupId]: {
                ...state.guilds[action.payload.guildId].groups[action.payload.groupId],
                action.payload.heroes,
              },
            },
          },
        },
      };
  }
}

If you ever come across such a reducer during a Code Review there is no doubt you will have troubles to make sure that no subpart of the state is mutated by mistake.

Whereas the same function can be written with ImmutableJS in a much simpler way

function reducer(state = defaultState, action) {
  switch (action.type) {
    case 'SET_HEROES_TO_GROUP':

      return state.mergeDeep(
        state,
        { guilds: { groups: { heroes: action.payload.heroes } } },
      ).toJS();
  }
}

Conclusion for Readability

ImmutableJS obviously wins this criteria by far if your state is nested.

One counter measure you can take is to normalize your state, for example with normalizr.
With normalizr, you never have to change your state on more than two levels of depth as shown on below reducer case.

// Defensive copying with spread operator
case COMMENT_ACTION_TYPES.ADD_COMMENT: {
  return {
    ...state,
    entities: { ...state.entities, ...action.payload.comment },
  };
}

// ImmutableJS
case COMMENT_ACTION_TYPES.ADD_COMMENT: {
  return state.set('entities', state.get('entities').merge(Immutable.fromJS(action.payload.comment)));
}

Second Criterion, Maintainability and Preventing Bugs: Immutable Wins Again!

A question I already started to answer earlier is: Why must our state be immutable?

  • Because redux is based on it
  • Because it will avoid bugs in your app

If for example your state is:

const state = {
  guilds: [
    // list of guilds with name and heroes
    { id: 1, name: 'Guild 1', heroes: [/*array of heroes objects*/]},
  ],
};

And your reducer case to change the name of a guild is:

switch (action.type) {
  case CHANGE_GUILD_NAME: {
    const guildIndex = state.guilds.findIndex(guild => guild.id === action.payload.guildId);

    const modifiedGuild = state.guilds[guildIndex];
    // here we do a bad thing: we modifi the old Guild 1 object without copying first, its the same reference
    modifiedGuild.name = action.payload.newName;

    // Here we do the right thing: we copy the array so that we do not mutate previous guilds
    const copiedAndModifiedGuilds = [...state.guilds];
    copiedAndModifiedGuilds[guildIndex] = modifiedGuild;

    return {
      ...state,
      guilds: copiedAndModifiedGuilds,
    };
  }
}

After doing this update, if you are on a detail page for Guild 1, the name will not update itself!
The reason for this is that in order to know when to re-render a component, React does a shallow comparison, i.e. oldGuild1Object === newGuild1Object but this only compares the reference of those two objects.
We saw that the references are the same hence no component update.

An ImmutableJS data structure always returns a new reference when you modify an object so you never have to worry about immutability.
Using spread operators and missing one level of copy will make you waste hours looking for it.

Another important issue is that having both javascript and Immutable objects is not easily maintainable and very bug-prone.
As you cannot avoid JS objects, you end up with a mess of toJS and fromJS conversions, which can lead to component rendering too often.
When you convert an Immutable object to JS with toJS, it creates a new reference even if the object itself has not changed, thus triggering component renders.

Conclusion for Maintainability

Immutable ensures you cannot have immutability related bugs, so you won’t have to check this when coding or during Code review.

One way to achieve the same without Immutable would be to replace the built-in immutability with immutability tests in your reducers.

it('should modify state immutably', () => {
  const state = reducer(mockConcatFeedContentState, action);

  // here we check that all before/after objects are not the same reference -> not.toBe()
  expect(state).not.toBe(mockConcatFeedContentState);
  expect(state.entities).not.toBe(mockConcatFeedContentState.entities);
  expect(state.entities['fakeId']).not.toBe(mockConcatFeedContentState.entities['fakeId']);
});

But making sure that your team-mates understand and always write such tests can be as painful as reading spread operators filled reducers.

My opinion is that Immutable is the best choice here on the condition that you use it as widely as possible in your app, thus limiting your use of toJS.

Third Criterion, Learning Curve: One point for Spread Operators

One important point when assessing the pros and cons of a library/stack is how easy will it be for new developers to learn it and to become autonomous on the project.

The results of my analysis on half a dozen projects using is that learning ImmutableJS is hard work.
You have a dozen data structures to choose from, about two dozen built-ins or methods that sometimes do not behave the same way javascript methods do.

Below are some examples of such differences:

const hero = {
  id: 1,
  name: 'Superman',
  abilities: ['Laser', 'Super strength'],
}

const immutableHero = Immutable.fromJS(hero); // converts objects and arrays to the ImmutableJS equivalent


// get a property value
hero.abilities[0] // 'Laser'
immutableHero.get('abilities', 0) // 'Laser'

// set a property value
hero.name = 'Not Superman'
immutableHero.set('name', 'Not Superman')

immutableHero.name = 'Not Superman' // nothing happens!

// Number of elements in an array / Immutable equivalent
hero.abilities.length // 2
hero.get('abilities').size // 2

// Working with indexes
const weaknessIndex = hero.abilities.indexOf('weakness') // -1
hero.abilities[weaknessIndex] // throws Error

const immutableWeaknessIndex = immutableHero.get('abilities').indexOf('weakness') // -1
immutableHero.get('abilities').get(weaknessIndex) // 'Super strength'

While you can use all the knowledge you have on javascript and ES6, if you go with ImmutableJS you’ll have to learn some things from the start.

Nicolas, a colleague of mine once came to me with a strange issue.
They were using normalizr and had a state that looked like the Immutable equivalent of this:

{
  fundState: {
    fundIds: // a list of fund ids
    fundsById: // an object with a fund id as key and the fund data as value: { fund1Id: fund1 },
  }
}

Their problem was that their ids, indexing funds in fundsById Map where strings of numbers and not numbers.
At least twice, one of their developers had a hard time writing a feature because they were trying to get the funds like this: state.get('fundState', 'fundsById', 3) to get the fund of id 3.
The issue here is that contrary to javascript, strings of numbers and numbers are not at all interchangeable (it may be a good thing but it is an important difference!).
So they had to convert all their id keys to the right type.

Another issue that colleagues shared with me was that ImmutableJS is really hard to debug in the console as shown below with our immutableHero object from above:

immutable-hero-console-no-formatter

As you can see, it’s nearly unreadable and its only a really simple object!

A great solution I encountered when trying to help them is immutable formatter a chrome extension that turns what you saw into this beauty:

immutable-hero-console-formatted

To enable it, you have to open chrome dev tools. Then access the dev tools settings and check “enable custom formatter” option:

Capture d’écran 2018-04-16 à 18.33.10

In the case of ES6, new developers have three things to learn:

  • Understand why immutability is important and why they should bother
  • How to use spread operators to enforce immutability
  • Not to use object.key = value to modify their state

Conclusion for Learning Curve

Overall the learning curve for spread operators, an ES6 tool is rather easy since you can still use all the javascript you know and love but you must be careful to the points listed above.

ImmutableJS on the other hand will be much harder to learn and master.

Conclusion for Part 1

In conclusion, this first part showed us that ImmutableJS comes with a lot of nice things, allows you to concentrate on working on value added work rather than trying to read horrible reducers or looking for hidden bugs.
This of course is at the cost of the steeper learning curve of a rich API and some paradigms different from what you are used to!

In the part II of this article I will compare both solutions in the light of Performance and compatibility with typing.

If you liked this article and want to know more, follow me on twitter so you know when the second part is ready :).
@jeremy_dardour


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • Wyatt Greenway

    Your article slightly confuse me: You say the spread operator is ugly and prone to bugs, but immutable is better because “Whereas the same function can be written with ImmutableJS in a much simpler way…” with `deepMerge`. Why not just create (or use one of the many third party versions) of the same deepMerge for raw objects without the need for an entire library? It appears as though you are saying “This library looks amazing when you compare it with the wrong way of doing things!”. Don’t forget that ImmutableJS (and redux) in general are hard on the garbage collector.

    Also, you might consider looking into redux-panoptic. It looks more like what you are looking for (disclaimer, this library was created by me and some fellow coworkers because of the similar things we disliked about redux): https://www.npmjs.com/package/redux-panoptic

  • Ronni Egeriis Persson

    This small lib is a great alternative, for those who don’t want to bring in Immutable.js https://github.com/mariocasciaro/object-path-immutable

  • Jérémy Dardour

    Thank you for your comment!

    In this article I focus on the two options we had in mind to answer the Immutability purpose.
    I considered the two most popular options (that we were using on projects) criterion by criterion and displayed what I observed. This horrible reducer example is extracted from a real project so that is something that can happen.

    Since I haven’t studied other options, I don’t know what are their advantages and drawbacks but I believe there are some drawbacks too.

    I think considering spread operators “The wrong way of doing things” is a bit strong since so many people do it this way but I get your message.

    About the garbage collector I did not know and will investigate this!

    Thanks for the library, I will check it :)

  • Jérémy Dardour

    Thank you for your comment Ronni !
    I will check what this lib is about :)

  • bubixl

    If your goal is to get rid of all the boilerplate that comes with redux, you should have a look at rematch: https://github.com/rematch/rematch

  • bubixl

    I also feel that we’re comparing two extremes here. Why not simply clone your state with lodash’s cloneDeep function at the beginning of your action? Or better: just use dot-prop-immutable as suggested in the docs of redux: https://github.com/debitoor/dot-prop-immutable

    I’m pretty sure that using immutable data structures can prevent many bugs to happen, but I’m not convinced about the examples provided here (and it is very hard demonstrate such thing anyway). As people mentioned, they are some easier ways to avoid modifying the existing state. I think that the second part of this article should focus on problems that Immutable.js solves better than some other simpler libraries. Because here, it just proves itself more complicated than existing alternatives.

  • boubiyeah

    This is indeed another possibility, if you don’t care about performances at all. Deep cloning is very slow (not a problem if your model is simple or if actions are not dispatched at a high frequency) and make optimizations such as using PureComponent impossible. lastly, and for those who care, most libs that offer to update a model via a nested path string are terrible at providing type safety.

  • Wyatt Greenway

    No problem! Also, I meant no insult at all in what I said. Simply that when I am programming, if something is tedious or bug prone, I consider it “the wrong way”, and discover a better way to go about things.

  • bubixl

    Totally agree here. There is a trade-off between type safety and simplicity when it comes to path strings. And you’re right when you say that deep cloning as being very slow, but how slow is it in reality? How often does deep-cloning become the bottle neck of your app? I guess it depends on how often your state is modified.
    I honestly didn’t think about pure components, and I think it is a valid concern. Thanks for your remarks!

  • bubixl

    For path string, I’ve just discovered this: https://github.com/bsalex/typed-path
    You have type safety with a slightly more verbose syntax than for path string.

  • Nice article. I’m not quite good at reading things in English quickly so for the first glance at the title ‘How not to … succeed with ImmutableJS…’ I thought it is about why we don’t want to use ImmutableJS lol.

    So yeah, immutability is a must-have for redux apps and having a dedicated library DOES help a lot in code readability and avoiding bugs. Totally agreed with that. But there’re also two other things that hold us back when our team were comparing ImmutableJS and other libs(seamless-immutable, the one we use at project atm): maintainability and learning curve.

    Maintainability I mean more about the invasion it could do to your redux app. Not only reducers, your React components(and perhaps more other layers like you api handlers, selectors, etc) will now have to know about ImmutableJS(you have to `toJS()` at some point inside your components). And this could make replacing it more difficult some time in the future. Your app will be tightly coupled with ImmutableJS.

    Learning curve. Besides the differences of API you mentioned in the article, the fact that people should now know more contexts of ImmutableJS even if they are writing something looks completely unrelated also adds burden to the developers. For example, they should know about ‘not to use `toJS()` in `mapStateToProps()` to avoid unnecessary re-renders’ due to how the `toJS()` API is implemented by ImmutableJS even if they’re just connecting data to the component(thought it’s unrelated to reducer immutability). Such things are hard to be remembered every time, hard to be lint-ed(or can we?) by linting tools and hard to debug once they happen. And I think this is a bad smell for such a architecture that you should care about details from another layer when writing codes in another mentally separated layer(actions, reducers, sagas, selectors, container components, pure components in a normal redux app architecture). You gotta think more and be more careful when writing any code, and I think this is somehow not developer friendly.

    There’re ways to solve these problems I mentioned above of course but yeah those are two main concerns we have when choosing a immutability lib for our redux app. Maybe it just works well in your team :)