Now, this is obvious because all the code is in the same place. Now imagine the surprise if we import that function from somewhere else:
There are several approaches to resolving the issues presented by mutation, some better than others. But, unfortunately, the worst one (and one of the most common solutions) is to make a copy of the object before passing it to a function:
The problem is that we’re transferring the responsibility of avoiding mutations to the consumer of our functions. So every time someone uses those functions, they need to make sure they do a copy of the object before.
One better solution is to write our functions without mutations, returning updated copies of the received objects instead of changing them. These are called pure functions, and the action of avoiding mutations is called immutability. Going back to the first example:
This is great for small functions that do little changes to small objects, but the problem comes with nested values, which increase the complexity greatly:
Which is way more complex than just doing a mutation:
So developers tend to “fall back” to doing mutations or doing a copy. Luckily
for us, if we want to write code as we were doing mutations, but without them,
we have an excellent library for that called immer. This library allows
us to write our
updateOtherValue function like this:
Array has a
few methods in its prototype, like
pop, that mutate the array. So we
end up with similar issues to the first example:
We can either avoid impure methods:
Or, we can resort to immer again:
To avoid mutations in Arrays in the future, I recommend this excellent site with a comprehensive list of Array methods and a flag for when they mutate or not: doesitmutate.xyz.
Another thing to consider is that the DOM APIs, such as
are full of mutations, so if we want to change something dynamically on a WebApp
we need to mutate. Luckily for us, libraries like Preact, React, Vue, and others
have an abstraction layer over the DOM that makes the DOM behave in a “pure” way
by letting us update its state without having to do the mutations ourselves,
consistently and safely.
If we use classes, we need mutations!
This article is in the same series as We don’t need classes and is very close to it in spirit. Classes generally encourage saving and updating values inside them, so this is yet another reason to avoid classes and to use pure functions and values instead. But even if we still use classes, we should avoid mutations, by returning new instances of the classes with the new values in them.
What about performance?
Next time we are about to do a mutation, we should ask ourselves: Do we need to mutate that value? Don’t we have a way of resolving that issue without doing mutations? I’m not saying this will always be the solution, but it should be the default.