7

Why does map mutate array of objects?

var obj = {
  items: [{
    value: 1,
    selected: true
  }, {
    value: 2,
    selected: false
  }]
};

var items = obj.items.map(i => {
  if (i.value === 2) i.selected = true;
  return i;
});

console.log(obj);

6
  • 5
    Because it's not making a copy of the object. Commented Jun 22, 2018 at 14:38
  • 1
    Because you're mutating objects. map doesn't create a copy of those objects. Commented Jun 22, 2018 at 14:38
  • Every i is still an element of your obj.items array, if you change it via i.selected = true, you change the element itself. If you don't want that behaviour, just copy it. Commented Jun 22, 2018 at 14:40
  • @Federico klez Culloca, I haven't found any explanation of that on internet... You was the first who answered, can you add an answer and I will mark it accepted? Commented Jun 22, 2018 at 14:45
  • @terreb Hammerbot's answer is already correct, no need to add mine :) I suggest you mark his as correct. Commented Jun 22, 2018 at 14:46

4 Answers 4

7

If you want a quick solution for an unmutable version of .map over an array of objects you can use the spread operator:

myArrayOfObjects.map(({...obj}) => { });

Example:

const foo = [];

for(let i = 0; i < 5; i++) {
    foo.push({label: "foo"});
}

const bar = foo.map(({...val}) => {
    val.id = Math.random();
  return val;
});

console.log(foo);
console.log(bar);
Sign up to request clarification or add additional context in comments.

1 Comment

Why does spreading the mapped value not mutate the original object?
5

When you map an array, it's not creating a copy of the object. It's just iterating over the array.

If you don't want to mutate the object, you have to create a copy of the object:

var items = obj.items.map(item => {
    let i = JSON.parse(JSON.stringify(item))
    if (i.value === 2) i.selected = true;
    return i;
});

2 Comments

If it's an array of objects. If it's an array of integers it will make a copy
Yes, but given the question I think it bears repeating.
1

.map() as Hammerbot explained, does not create a copy, it creates a new array which directly references your object, thus mutating your object.

If you don't want to mutate your object, you can use Object.assign() within your mapping which creates a copy of the object.

1 Comment

And what if the object you are copying using ‘Object.assign’ has objects in it? Those child objects are still a reference to those in memory in the original array. ‘Object.assign’ only does a shallow copy.
0

First for reference...

.map is an Array method and not an Object method. When .map is appropriately utilized on an Array, it will return a new array with each element being the result of the callback function. Check out the first example in MDN Web Doc for Array.prototype.map() There it states, in the description, "the map() method is a copying method" which basically means it will not mutate the original array. Instead it will essentially iterate over each item from the array, perform some expression (through the callback function), then return the result value as a single element into a new array. This isn't exact but paints the picture. I recommend reading Copying Methods and Mutating Methods and shallow copy to better understand how exactly the "copying" works. After iterating through all the elements of an array the new array will be returned containing the new element values.

The Juicy Part... Now, attempting to utilize the map() method on an object does not create a copy of the object. Instead you would be directly referencing the object and directly mutating its properties. When working with Objects in such a way you can utilize it's native methods like Object.assign, Object.keys, Object.values, or Object.entries to create an array to then utilize .map() on. Here's a nice article with some examples. Another helpful item is the for...in statement. You can utilize this to iterate over an objects properties then execute some code to either perform a check or modify the property value. Below is an example.

let names = ["Superman", "Batman"];

let players = names.map((name) => ({
  Name: name,
  Score: 0,
  IsActivePlayer: false
}));

players.forEach((player) => {
  for (const prop in player) {
    if ((prop === "Name") && (player[prop] === "Superman")) {
      console.log("I do bleed");
      break;
    }
  }
});

Some last items I would suggest is reading on the subject of the spread syntax for object literals. Here are some supplemental reads. Object spread vs Object.assign understanding spread syntax

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.