Immutability through Mutability: Immer & Redux
It’s easy for things like your Redux state to turn into a nested mess. If you want to maintain immutability, you either have to use a specialized library like Immutable.JS which has its own API for manipulating objects, or you do essentially the following:
This doesn’t only look bad ( arrow code anyone? ) but it also feels really bad 😞. You waste so much time cloning things and if you screw up then you just lost all the benefits of immutability.
ImmutableJS solves a lot of those issues, but it has an entirely different API to use when manipulating data and it’s not always intuitive (no spread, no dot syntax, uses strings to access nested objects, etc).
You also lose a lot of the performance advantages if you convert an ImmutableJS object to plain JS. Which means you either have to use the ImmutableJS API in your React code 😫 or you need to add a memoization layer with something like reselect to keep the performance impact down😐.
The other solution — normalization is often difficult to implement, especially if your data sources aren’t on board. Transforming back and forth between normalized and nested data can be a nightmare for the uninitiated.
A few days ago, Michel Weststrate (the creator of MobX) came out with a brilliant little library called Immer (German for “Always”). It uses ES6 Proxies on objects “produced” with it to clone modified sections and return that cloned object as a response.
Here’s the same logic as above but this time using Immer:
How I use it
One minor issue not noted in the Immer documentation, is that mutation means we’re no longer returning from every case in our switch statements. This means we have to use
break... which is not generally regarded as a smart move 😕.
Break is the closest thing we have today to the old
goto method in assembly language programming, and there's a reason we don't expose that to modern languages.
Goto relations aside, however, it's just incredibly easy to forget. In fact, despite being aware of this, I totally forgot to put it in when I was first testing out Immer, which resulted in a few very confused minutes while I tried to figure out why nothing was working (both my creation and deletion actions were running every time, so nothing ever changed).
Since I don’t like re-writing boilerplate, I’ve created the following function:
This does make the assumption that your actions are similar to this standard with a type and payload.
You can then use it as follows:
Which is a whole lot nicer than the standard spread cloning method:
ImmutableJS isn’t too bad on the boilerplate side of things, but its use of strings for merging deep changes is a bit scary as your compile time checks won’t be able to help you spot errors.
I’m a big fan, but there are some limitations with the library right now
- Proxies aren’t yet in Internet Explorer or Android’s React-Native so Immer falls back to a much less performant es5 version 😕
- If you opt not to use switch (like I have) then you do lose the nice fall-through syntax that switch statements provide.
- If you’ve gotten into the habit of only using immutable functions (slice instead of splice, etc) you’ll have to brush up on their mutable counterparts.
The concepts of immutability and the boilerplate are always blockers when I try to teach a fellow developer how to use Redux. I think Immer can finally reduce that cognitive overload when first learning, and I’m excited about its potential.
As always, feel free to comment below or tweet me @Tetheta. Let me know if this was useful to you and what you think about Immer’s “immutability through mutability.”
Written with StackEdit.