Not much explanation here so Ill just get straight to the point.
For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
and what is the best way to update objects in an array? is items2[i]
ideal?
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable. The typical use case is to execute side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign
for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign