Immutable array updates with Array.prototype.with

Browsers recently gained a new interoperable method that you can call on Arrays: Array.prototype.with().

Browser Support

  • Chrome: 110.
  • Edge: 110.
  • Firefox: 115.
  • Safari: 16.

Source

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:

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.