Briefly
The with
method changes the value of one of the elements in an array and returns a new array, without modifying the original array.
Example
Let's change the element 'white' to 'blue':
const colors = ['red', 'green', 'white']const newColors = colors.with(2, 'blue')console.log(newColors)// ['red', 'green', 'blue']// The original array remains unchangedconsole.log(colors)// ['red', 'green', 'white']
const colors = ['red', 'green', 'white'] const newColors = colors.with(2, 'blue') console.log(newColors) // ['red', 'green', 'blue'] // The original array remains unchanged console.log(colors) // ['red', 'green', 'white']
Let's change the element 'white' to 'blue', using a negative index:
const colors = ['red', 'green', 'white']const newColors = colors.with(-1, 'blue')console.log(newColors)// ['red', 'green', 'blue']// The original array remains unchangedconsole.log(colors)// ['red', 'green', 'white']
const colors = ['red', 'green', 'white'] const newColors = colors.with(-1, 'blue') console.log(newColors) // ['red', 'green', 'blue'] // The original array remains unchanged console.log(colors) // ['red', 'green', 'white']
How it works
Array
takes two arguments:
- the index of the element to be changed;
- the new value of the element to be changed.
The index of the element can be:
- positive — for accessing elements from the beginning of the array;
- negative — for accessing elements from the end of the array. For example,
-1
— the index of the last element,-2
— the second to last, and so on.
Array
returns an array with the modified element.
Calling with
without arguments will not result in an error. This is equivalent to calling with
:
const routes = ['/home', '/settings', '/about']const newRoutes = routes.with()console.log(newRoutes)// [undefined, '/settings', '/about']
const routes = ['/home', '/settings', '/about'] const newRoutes = routes.with() console.log(newRoutes) // [undefined, '/settings', '/about']
Attempting to call with
with an index value outside acceptable values (index >
) will result in a Range
.
const authors = ['F. Kafka', 'J. Salinger']try { console.log(authors.with(2, 'K. Vonnegut'))} catch (err) { console.error('Caught an error! Here it is: ', err.message)}// Caught an error! Here it is: Invalid index : 2
const authors = ['F. Kafka', 'J. Salinger'] try { console.log(authors.with(2, 'K. Vonnegut')) } catch (err) { console.error('Caught an error! Here it is: ', err.message) } // Caught an error! Here it is: Invalid index : 2
How to understand
When working with arrays, sometimes you need to obtain a new array containing a modified copy of the original array. For example, let's write a function to change the value of the first element of an array using the with
method:
const updateFirstItem = (array, value) => { return array.with(0, value)}
const updateFirstItem = (array, value) => { return array.with(0, value) }
Let's check how it works and ensure that the original array remains unchanged.
const bears = ['grizzly', 'polar', 'brown']const result = updateFirstItem(bears, 'panda')console.log(result)// ['panda', 'polar', 'brown']console.log(bears)// ['grizzly', 'polar', 'brown']
const bears = ['grizzly', 'polar', 'brown'] const result = updateFirstItem(bears, 'panda') console.log(result) // ['panda', 'polar', 'brown'] console.log(bears) // ['grizzly', 'polar', 'brown']
Here is a comparison with a function that changes the value of the first element in the original array:
const updateFirstItemDanger = (array, value) => { const result = array result[0] = value return result}
const updateFirstItemDanger = (array, value) => { const result = array result[0] = value return result }
Calling this function will return the same result, but it will modify the original array. The update
function cannot be considered a pure function as it leads to changes in data not belonging to it (defined outside its context). In functional programming, such a change is called a side effect. Functions that cause side effects have a number of drawbacks: less predictability, difficulty in testing. In most cases, such code should be avoided.
const bears = ['grizzly', 'polar', 'brown']const result = updateFirstItemDanger(bears, 'panda')console.log(result)// ['panda', 'polar', 'brown']console.log(bears)// Oh no, where has the grizzly bear gone!// ['panda', 'polar', 'brown']
const bears = ['grizzly', 'polar', 'brown'] const result = updateFirstItemDanger(bears, 'panda') console.log(result) // ['panda', 'polar', 'brown'] console.log(bears) // Oh no, where has the grizzly bear gone! // ['panda', 'polar', 'brown']
Note: The reason the original array changes when using the update
function is that the function does not copy the original array received as an argument into a new one, but creates yet another reference to the original array. More about this can be read in the section on “Reference and value storage”.
Tips
💡 with
is a convenient way to avoid modifying (mutation) the original array.
💡 with
simplifies changing elements if they need to be counted from the end of the array rather than from the beginning. For example, this is convenient for changing the last element. Usually, to change the last element of an array, you need to determine its index from the beginning of the array. This requires:
- knowing the length of the array;
- calculating the last element's index using the formula
last
.Index = array Length - 1
Changing the last element using the traditional approach:
const words = ['first', 'second', 'third']const lastIndex = words.length - 1// Creating a copy of the arrayconst newWords = [...words]newWords[lastIndex] = 'last'console.log(newWords)// ['first', 'second', 'last']
const words = ['first', 'second', 'third'] const lastIndex = words.length - 1 // Creating a copy of the array const newWords = [...words] newWords[lastIndex] = 'last' console.log(newWords) // ['first', 'second', 'last']
Here’s how to do it using with
:
const words = ['first', 'second', 'third']console.log(words.with(-1, 'last'))// ['first', 'second', 'last']
const words = ['first', 'second', 'third'] console.log(words.with(-1, 'last')) // ['first', 'second', 'last']
💡 with
can be used when chaining array processing functions. Each method in the chain receives the result of the previous one. For example:
const days = ['', 'Mon', 'Tue', 'Wed', 4]console.log( days .filter(item => typeof item === 'string') .with(0, 'Sun') .map(item => item.toUpperCase()))// [ 'SUN', 'MON', 'TUE', 'WED' ]
const days = ['', 'Mon', 'Tue', 'Wed', 4] console.log( days .filter(item => typeof item === 'string') .with(0, 'Sun') .map(item => item.toUpperCase()) ) // [ 'SUN', 'MON', 'TUE', 'WED' ]
💡 When creating a new array, with
will convert all undefined cells to undefined
:
const numbers = [0, , 11, 20, , 30]console.log(numbers.with(2, 10))// [ 0, undefined, 10, 20, undefined, 30 ]
const numbers = [0, , 11, 20, , 30] console.log(numbers.with(2, 10)) // [ 0, undefined, 10, 20, undefined, 30 ]
💡 Support for the with
method in major browsers and Node.js has appeared relatively recently. For example, attempting to use with
in Node.js v.18.19.0 will result in an error:
const array = ['night','street','lantern']try { console.log(array.with(-1,'January'))} catch (err) { console.error('Caught an error! Here it is: ', err.message)}// Caught an error!// Here it is: array.with is not a function
const array = ['night','street','lantern'] try { console.log(array.with(-1,'January')) } catch (err) { console.error('Caught an error! Here it is: ', err.message) } // Caught an error! // Here it is: array.with is not a function