Approaches to Immutability
Adam Klein
Last updated on Sep 25, 2017

Immutability is a concept that's easy to fall in love with, but it's not always easy and straightforward to exercise. Here we will explore a set of tools and strategies that make the task easier.

Plain JavaScript

Using plain JavaScript requires no external library and no extra knowledge, but it requires way too much boilerplate. For example, setting a nested property in the state:

{
  ...state,
  parent: {
    ...(state.parent || {}),
    key: 'newValue'
  }
};

Too much boilerplate for such a simple and common task.

ImmutableJS / seamless-immutable

Both ImmutableJS and seamless-immutable are libraries that wrap plain objects with classes that enforce Immutability. Which means you simply cannot mutate the objects, even by mistake.

Example using ImmutableJS:

import { fromJS } from 'immutable'

const state = fromJS({}) // Must wrap the object with Immutable
state.parent.key = 'newValue' // => Throws error

return state.setIn('parent.key', 'newValue') // Returns a new object

The API gives you a decent set of tools for changing and accessing the object.

Because you are not working with plain objects, integration with 3rd-party tools might require special attention, e.g.: when you use redux devtools to import or export the state, redux-persist to save and restore parts of it, when you use combineReducers, and basically every 3rd-party tool that serializes and deserializes the state, or uses methods that work on plain objects but not on Immutables.

This can be quite annoying, and you may find yourself battling with the tool instead of celebrating it.

Bottom line: if you are willing to sacrifice the disadvantages for the sake of enforcing immutability - choose one of these libraries.

lodash/fp

This is the functional-programming version of the famous lodash library. Each lodash method has an equivalent lodash/fp method, just with the object passed as the last parameter instead of first:

// lodash
_.set(obj, key, value)

// lodash/fp
_.set(key, value, obj)

Changing a nested property with lodash/fp:

import { set } from 'lodash/fp'

set('parent.key', 'value', state)

Updating a property based on its current value:

import { update } from 'lodash/fp'

update('parent.key', value => value + 1, state)

Functions are automatically curried, which means these calls are equivalent:

import { set } from 'lodash/fp'

set(key, value, obj)
set(key, value)(obj)

This also means you can chain operations like this:

import { flow, set, update } from 'lodash/fp'

flow([update('parent.key', value => value + 1), set('parent.key', 'value')])(
  state
)

Bottom line: if you are willing to sacrifice enforcing immutability for a very rich API, higher performance and need easy integration with 3rd-party libraries this is a very good solution.

The functional style can be odd in the beginning, but very rewarding after you get used to it.

Comparison Table

ImmutableJSlodash/fp
PerformanceLowerHigher
APIDecentVery rich
ImmutabilityEnforcedNot enforced (error prone)
3rd-party integrationMore difficultEffortless
Back to all articles

© 500Tech. Building high-quality software since 2012.