Get in touch

Send an email to: lammers@gmail.com.
Or find me online at: Github, Twitter/X

Change arrays by copy with ES2023

There has always been some inconsistency between the behaviour of array methods in JavaScript. Some of the methods are mutating and change the original array on which they are called, while others leave the original array untouched and return a new array.
Methods like map(), filter() and reduce() do not modify the original array; they create and return a new one instead. On the other hand, methods like sort(), reverse(), and splice() are mutating and change the original array.

Mutation is often undesirable

Mutation refers to the act of changing the state of an object or data structure after it has been created. And while is not necessary always bad, it does have the potential of leading to unexpected behaviour and bugs. For this reason it is often recommended to avoid mutation if possible. When working with state in React it is even a requirement: All state updates have to be immutable. The new state must be a completely new value otherwise it won't trigger at re-render.

To use the mutating array methods without changing the original array a common work-around is to first create a copy of the original array before calling the method on it.

const original = ['c', 'a', 'b']

// Use spread operator to clone (shallow) the original array
const sorted = [...original].sort()
console.log(sorted) // ['a', 'b', 'c']

// The original array is unchanged
console.log(original) // ['c', 'a', 'b']

While this works fine, it would be far more convenient if we didn't have to think about accidentally updating the original array and making sure to create a copy first. Thankfully ES2023 brings us a few new array methods that give us the comfort of knowing that the original array stays untouched when they are called. This means it is no longer necessary to copy the original array first.

ES2023 introduces 4 new array methods

Among the new features in ES2023 are 4 new array methods which do not mutate the original array:

  • toReversed: Reverse an array
  • toSorted: Sort elements in an array
  • toSpliced: Remove, replace or add elements in an array
  • with: Change value at array index

All these methods return a new array and leave the original array untouched. The names of the first 3 might look familiar; they are indeed the non-mutating versions of reverse(), sort() and splice(). Let's have a look at each of these methods, see what they do and how they compare to their mutating counterparts.

Array.prototype.toReversed

The toReversed() method, as the name suggests, returns a new array with the elements in reversed order. Unlike the reverse() method it does not mutate the original array.

const arr = ['a', 'b', 'c']
const reversed = arr.toReversed()

console.log(reversed) // ['c', 'b', 'a']
// The orignal array is untouched
console.log(arr) // ['a', 'b', 'c']

View MDN for more details about toReversed()

Array.prototype.toSorted

To sort an array without mutating the original array, the toSorted() method has been added. If it is called without argument it will return a copy of the array in alphabetical order.

const arr = ['c', 'b', 'd', 'a']
const sorted = arr.toSorted()

console.log(sorted) // ['a', 'b', 'c', 'd']

// The orignal array is untouched
console.log(arr) // ['c', 'b', 'd', 'a']

Its sorting behaviour is the same as the mutating sort() method, so only an array of strings can be reliably sorted by calling toSorted() without arguments. To sort anything other than strings we can provide a compare function.

const arr = [9, 13, 20, 7]
const sorted = arr.toSorted((a, b) => a - b)

console.log(sorted) // [7, 9, 13, 20]

// The orignal array is untouched
console.log(arr) // [9, 13, 20, 7]

View MDN for more details about toSorted()

Array.prototype.toSpliced

The toSpliced() method can be used to remove, replace or add items in an array without modifying the original array. It works similar to the original splice() method.

It accepts the following parameters:

  • start: The index at which to start changing the array
  • deleteCount (Optional): The number of elements to remove from start index.
  • item1, item2, ... (Optional): These items will be added to the array at the start index.
const arr = ['a', 'b', 'c', 'd']

// Insert new element at index 2
const inserted = arr.toSpliced(2, 0, 'x')
console.log(inserted) // ['a', 'b', 'x', 'c', 'd']

// Remove 2 elements at index 1
const removed = arr.toSpliced(1, 2)
console.log(removed) // ['a', 'd']

// Replace element at index 2 with 2 new elements
const replaced = arr.toSpliced(2, 1, 'x', 'y')
console.log(replaced) // ['a', 'b', 'x', 'y', 'd']

// Original array is untouched
console.log(arr) // ['a', 'b', 'c', 'd']

View MDN for more details about toSpliced()

Array.prototype.with

The with() method can be used to change the value at a specific index in an array. It takes two parameters: The index and the value that should be set at the given index.

const arr = ['a', 'b', 'c']
const newArr = arr.with(1, 'd')

console.log(newArr) // ['a', 'd', 'c']

// The orignal array is not mutated
console.log(arr) // ['a', 'b', 'c']

This can be seen as the non-mutating equivalent of the bracket syntax to replace a value: arr[index] = newValue.

View MDN for more details about with()

Can these new array methods be used today?

Even though ES2023 hasn't officially been released yet (May 2023, as of writing), some browsers have already implemented support for these new array methods. However not all major browsers support it yet (Looking at you Firefox). If you want to start using any of the new methods today, it is recommended to use a polyfill like the one from core-js.

The addition of the new array methods is a very welcome improvement to JavaScript and can help avoid unintentional mutations in our code. I'm looking forward to making this my go-to whenever I need to modify an array. If you prefer writing code that avoids mutations like me, then these methods will definitely make our lives easier.