Browsers recently gained a new interoperable method that you can call on Arrays:
Array.prototype.with()
.
This article explores how this method works and how to use it to update an array without mutating the original array.
Intro to Array.prototype.with(index, value)
The Array.prototype.with(index, value)
method returns a copy of the array it's
called on with the index
set to the new value
you provide.
The following example shows an array of ages. You'd like to create a new copy of the array while changing the second age from 15 to 16:
const ages = [10, 15, 20, 25];
const newAges = ages.with(1, 16);
console.log(newAges); // [10, 16, 20, 25]
console.log(ages); // [10, 15, 20, 25] (unchanged)
Breaking down the code:: ages.with(...)
returns a copy of the ages
variable
without modifying the original array. ages.with(1, …)
replaces the second item
(index = 1
). ages.with(1, 16)
assigns the second item to 16
.
This is how you were able to create a new copy of the array with a modification.
This is pretty useful when you want to make sure that the original array remains unchanged, and this article covers some of the use cases for this. But, for now, take a look at what would have happened had you used the bracket notation:
const ages = [10, 15, 20, 25];
const newAges = ages;
newAges[1] = 16;
console.log(newAges); // [10, 16, 20, 25]
console.log(ages); // [10, 16, 20, 25] (Also changed 🙁)
As you can see, the ages
variable was also modified in this example. That’s
because when you assign ages = newAges
, JavaScript does not copy the array but
rather creates a reference to the other array. So, any change in one will also
affect the other one as they are both pointing to the same array.
Array.prototype.with() and immutability
Immutability is at the heart of many frontend libraries and frameworks, to name a few: React (and redux), and Vue
Also, other libraries and frameworks don't necessarily require immutability but encourage it for better performance: Angular and Lit
So, developers often had to use other methods that returned copies of arrays which sacrificed code readability:
const ages = [10, 15, 20, 25];
const newAges = ages.map((age, index) => {
if (index === 1) {
return 16;
}
return age;
});
console.log(newAges); // [10, 16, 20, 25]
console.log(ages); // [10, 15, 20, 25] (Remains unchanged)
Here's an example Codepen of how .with()
can be used in React in combination
with useState to immutably update an array of items:
Since the .with()
method returns a copy of the array, you can chain multiple
.with()
calls or even other array methods. The following example demonstrates
incrementing the second and third ages from the array:
const ages = [10, 15, 20, 25];
const newAges = ages.with(1, ages[1] + 1).with(2, ages[2] + 1)
console.log(newAges); // [10, 16, 21, 25]
console.log(ages); // [10, 15, 20, 25] (unchanged)
Other new immutable methods
Three other methods recently became interoperable:
Array.prototype.toReversed()
which reverses the array without mutating the original array.Array.prototype.toSorted()
which sorts the array without mutating the original array.Array.prototype.toSpliced()
which works like.splice()
but without mutating the original array.
These three methods are, according to MDN, the copying version of their counterparts. These methods can also be used where immutability is expected or preferred.
In conclusion, immutable updates can be achieved more easily in
JavaScript with one of the four methods presented in this article. Specifically,
the .with()
method makes it easier to update a single element of the array
without mutating the original array.