This article was originally posted on medium here
TLDR; EcmaScript provides utilities to keep data immutable. These tools find their limits with nested structures. immutadot is a library to deal with nested immutable structures. Give it a try.
According to wikipedia an immutable object (unchangeable object) is an object whose state cannot be modified after it is created. This rule is quite simple, if you want to modify some property of an object you have to do it on a copy. We will see a bit later what improvements and fancy features it unlocks for our developments.
EcmaScript
EcmaScript provides utilities to keep our data immutable. Arrays and objects APIs contain methods to create copies and even prevent instances from being updated. More recently EcmaScript introduced a new syntaxes to create copies of objects and arrays.
Object.assign
We want to add a name property in our object.
const lutraLutra = {
commonNames: ['eurasian otter'],
}
We can do it with Object.assign
and a little trick. Basically it copies all properties from an object into another one, thus it mutates the target object. We use a small trick, passing an empty object as first parameter, which creates a new reference.
const newLutraLutra = Object.assign(
{},
lutraLutra,
{ name: 'Lutra lutra' },
)
We now have a new object with our new name
property and a commonNames
property remain unchanged. With this method you can create/overwrite multiple properties at the same time.
Array.concat
Now let's do it with an array. We want to add two new elements in an array in an immutable way.
const commonNames = ['eurasian otter']
Unlike Array.push
, Array.concat
does not mutate our array. Instead, it returns a new array.
const newCommonNames = commonNames.concat(
'european otter',
'common otter'
)
This method is flexible. It takes as many elements as you want. They can be either values or arrays.
Object.freeze
Object.freeze
isn't really familiar. It lets you make an object immutable! It prevents every type of mutation (creation, modification, deletion) induced by the use of setters.
let lutraLutra = {
commonNames: ['eurasian otter', 'european otter', 'common otter'],
name: 'Lutra lutra'
}
We will try to delete name
property after object has been frozen.
lutraLutra = Object.freeze(lutraLutra)
delete lutraLutra.name
Reallocation isn't necessary since the object passed as parameter has been made immutable by Object.freeze
. This method has two available modes:
- A non-strict mode that does not apply mutations
- A strict mode that throws a
TypeError
if you try to apply mutations
Be careful, it is not recursive. Our property commonNames
isn't immutable.
Spread operator
The spread operator syntax has been introduced in ES2015 for arrays and in ES2018 for objects. It copies all properties of a given object into a new object literal.
const newLutraLutra = {
...lutraLutra,
name: 'Lutra lutra',
}
With arrays, it copies all values of an array into a new array.
const newCommonNames = [
...commonNames,
'common otter',
]
it replaces nicely assign
and concat
, it is easily readable and creates a convention between arrays and objects. It is possible to spread multiple arrays and objects in a same literal.
Why use immutability?
You found out how to make objects and arrays immutable with JavaScript but we didn't explain yet why using immutability is so necessary nowadays. As developers, we are always looking for a way to write more maintainable and readable code. Some paradigms such as Functionnal Programming are focusing on this.
Functional programming’s goal is to allow us to think less and write more descriptive code.
It has a declarative approach of programming, which means that you focus on describing what your program must accomplish rather than how it should do it. It gives more meaning to your code so that the next developper can understand it more easily. Functional programming brings along other concepts that help reach this goal, such as immutability.
What are the benefits?
Does it sound like a hype term to you? Immutabilty brings many solutions to programming matters we ecounter everyday:
- Avoid side effects
- Data changes detection made simple (shallow comparison)
- Explicit data changes
- Memoization
- Memory optimization
- Better rendering performances
- Easy testing
« Unlike most trends in the world of JavaScript, data immutability is bound to stick with us for a while, and for good reason: firstly, because it’s not a trend: it’s a way of coding (and thinking in code) that promotes clarity, ease of use and understanding data flow, and makes code less prone to errors. »
In the last few years one of our biggest challenges has been to find an efficient way to detect changes in our data to determine whether or not we should render our interfaces. It's easy to detect changes between primitive values, but it's a completely different issue for objects and arrays. You could compare them by value but you would have to implement recursive algorithms and deal with potential issues like cyclical references. Another method would be to compare object references with the strict equality operator ===
. It's more effective and there isn't any risk to enter in some deathly infinity loop. That's why modern frameworks enforce this concept.
Highlighted by modern frameworks/libraries
Modern frontend frameworks and libraries are based on a concept that improves drastically performances. This is the well-known Virtual DOM. This technology has been created from a simple evidence: DOM manipulations are expensive.
Like explained, frontend frameworks and libraries chose to use immutability in order to improve their performances. Nowadays we have to deal with more and more data in our applications, and therefore more markups. So our browsers need to handle much more computations than 10 years earlier. DOM operations are expensive, modern frameworks tend to reduce the number of DOM updates.
Why do we need utility libraries?
As we saw earlier, the way to handle immutability in EcmaScript is made simple thanks to syntactic sugar but is quite limited in nested structures. With the arrival of libraries like redux
, nested structures have become more popular.
const animals = {
weasels: {
lutraLutra: {
commonNames: ['eurasian otter'],
},
},
}
const newAnimals = {
...animals,
weasels: {
...animals.weasels,
lutraLutra: {
...animals.weasels.otter,
name: 'Lutra lutra',
},
},
}
As you can see it becomes more tedious to write and harder to read. Simple use-cases like overriding an index of an array aren't easily achievable.
const lutraLutra = {
name: 'Lutra lutra',
commonNames: ['eurasian otter', 'european', 'common otter'],
}
const newCommonNames = [...lutraLutra.commonNames]
newCommonNames[1] = 'european otter'
const newLutraLutra = {
...lutraLutra,
commonNames: newCommonNames,
}
These reasons are sufficient to start finding out some tool that help focusing on what really matters, the meaning of your code. That's why we created immutadot, to help us keep javascript codebase readable and maintenable. Give it a try.
Top comments (9)
I gotta say, data-first & lack of auto-currying really hurts their API. Consider:
If I want to change my current city, as well as push a new one onto the list of cities, I have to do this:
Nothing here is reusable or composable. If the methods had taken their data last, and have been curried, we could've had both:
Which could be abstract out into:
Hi Steven!
Actually our API has a way to do this:
Here's your working example on runkit: runkit.com/embed/kr9e9tins87j
Anyway thank you for your comment, we're waiting for other constructive feedback from you.
That's great! Took a cursory look at the code, and if you'd reversed the argument order, you could've delegated to
pipe
(maybe a more familiar name for people?) andcompose
(right-associative version of pipe) fromlodash
,ramda
or whatever. The problem now is, what if I'm already piping a bunch of unary functions using any of the bigger FP toolbelts, and add aimmutadot
function? It won't work:Good API inspiration,
monocle-ts
: github.com/gcanti/monocle-tsJust trying to understand better. So Redux makes using nested structures more prolific, and nested structures don't work well with immutability (also I claim are hard to read), and immutadot ensures you can keep doing this and stay immutable? I have an honest few questions (because I lack in this area).
Should we be encouraging this nested structure and does immutadot actually help to make it more readable? Does readability still suffer and immutadot just a library to ensure you can keep doing this and bandaids the immutability issue?
Again, this is new territory for me.
Thank you for your question Eric!
First of all, we’re not encouraging the use of nested structures ; however if you have no choice, or if nested structures are a good fit for your needs, immutadot is here to help.
If you take this example from the article:
It can be done with immutadot like this:
It results in a more concise syntax. In my opinion immutadot also brings more meaning to operations by using well-known function names. We are mostly based on ES2015+ so it’s easy to learn.
Hope it helps. Don’t hesitate to ask if you have more questions!
That absolutely answers and yes, it is obviously more readable. I guess the next step is for me to try and apply it to some of my own code. So it, in fact, replaces the nested structure with something more declarative and moves the harder to read nested stuff out replaced with something that should be more readable and also probably reduces the possibility of syntax errors in that nested structure. I like it the syntax of
set()
and how it's still readable!Great article. BTW, for React state immutability, i wrote an article
dev.to/ganeshmani/immutablejs-for-...
great post thank you!