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.
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.
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.
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.
ImmutableJS | lodash/fp | |
---|---|---|
Performance | Lower | Higher |
API | Decent | Very rich |
Immutability | Enforced | Not enforced (error prone) |
3rd-party integration | More difficult | Effortless |