Immutable.js’s `toJS` Should be Avoided
Over the last few years working in front-end software development, I’ve noticed several anti-patterns that can be detrimental to applications, and hard to reverse. This is the third article in a series of opinionated posts about front-end anti-patterns that should be avoided. You can read the previous article here.
Background
To understand this anti-pattern, one needs to understand how object references work in JavaScript. Consider this code snippet:
const a = { apple: "red" };
const b = { apple: "red" };a === b // evaluates false
Why is this false?
When an object is created in JS and stored in a variable, the variable actually stores a “reference” to the object, not the value of the object itself. So in the above example, two different objects are being created, and then the references to those objects are stored in two separate variables.
You can also think of it like this: when an object is created, the variables store addresses to those objects.
const a = { apple: "red" }; // a = Address-#123
const b = { apple: "red" }; // b = Address-#456a === b // this is essentially: Address-#123 === Address-#456
If you think about it like this, it’s easier to see why it evaluates to false. It’s comparing two different addresses/references/pointers together. On the contrary, this does what you’d expect:
const a = { apple: "red" }; // a = Address-#123
const b = a; // b = Address-#123a === b; // Evaluates to true, since Address-#123 === Address-#123
The Anti-Pattern
With that out of the way, we can get to the reasons why immutable.js
'stoJS
is bad. It stems from this basic statement: toJS
always generates new object references.
This means if I have this map:
const myMap = Map({ apple: "red" });
Then this will always evaluate to false
:
myMap.toJS() === myMap.toJS()
Many front-end libraries depend on being passed the same object reference when data has not changed.
For example, in React, if you’re using PureComponent / memo
, then React will not re-render if the props have not changed. But to tell if the props have changed, React does a shallow equality check, which is just a fancy way of saying React uses ===
like in the above examples. This means your component will always re-render if you pass some data that you got using toJS
.
// `BlogPosts` below will always re-render, even if `posts`
// has not changed. This is because `toJS` is always returning a
// brand new object reference, which means `BlogPosts` always thinks
// the data has changed.
<BlogPosts
posts={posts.toJS()}
/>
As another example, reselect
will only re-compute a selector if the results of the input selectors changed. If you use toJS
, reselect will always think the data has changed.
// In this example, the output of selectBlogState will always be
// different
const selectBlogState = state => state.get("blogs").toJS();// Because the result of the input selector here is always
// different, then `selectBlogPosts` always re-calculates.
const selectBlogPosts = createSelector(
selectBlogState,
(blogState) => blogState.posts.map((post) => {
// some complex perf intensive task here
});
);
Final Thoughts
One can imagine the effects of wide-spread use of toJS
in a react/redux/reselect application. You can end up in a situation where when one small part of your redux state changes, most of the app ends up re-rendering. Even the redux docs mention this problem. For the above reasons, toJS
should be avoided as much as possible.
This is the third article in a series of opinionated posts about front-end anti-patterns that should be avoided. You can read the previous article about avoiding global css styling here.