liron29
liron29

Reputation: 82

React - deep copy arrays and objects problem

I have a state array that I want to perform actions on and then update the state, but it seems like I'm missing a deep copy somewhere and the state get updated without setState which causes me problems (I know that because I see the state gets updated even without the setState line) .

const itemsToRemove = ['Pepsi', 'Fanta'];
let temp = [...this.state.categories];
let categoryToUpdate = temp.find((category) => category.name == 'drinks');
categoryToUpdate.items = categoryToUpdate.items.filter((item) => {
  return !itemsToRemove.includes(item);
});
//this.setState({categories: temp})

After those lines I would like categories state in object with "drinks" name to still include the items in itemsToRemove because I did not update the state but they still get removed from the state.

categories state's structure is as follows:

categories:[{name: String, items: Object}...]

Upvotes: 1

Views: 979

Answers (1)

Zsolt Meszaros
Zsolt Meszaros

Reputation: 23160

You guessed it right, you still mutate the items array when you update the copy.items directly. Although you used the spread operator, it only goes one level deep. It only creates a shallow copy of the array.

It works great on primitive data types (strings, numbers, etc.) but when you have nested objects (arrays) and you copy it, these nested objects inside that object will not get copied as they are only references.

const original = [{
  name: 'Name 1',
  items: ['1', '2', '3']
}, {
  name: 'Name 2',
  items: ['11', '12', '13']
}, 99]
const copy = [...original]

// notice how the original third element won't change to 100
copy[2] = 100
// while all the items in the second element will
copy[1].items = ['21', '22', '23']
// and if you update a string inside the object
// that will be changed too as it's inside an object
copy[1].name = 'Updated name 2'

console.log('updated', copy)
console.log('original', original)

You can use Array.map() to create a new array from the categories array and as you loop through the elements, you can simply return a new object using the spread operator if you want to update them.

Check the snippet below:

const itemsToRemove = ['Pepsi', 'Fanta'];
const categories = [{
    name: 'drinks',
    items: ['1', '2', 'Pepsi', '4', 'Fanta', '6']
  },
  {
    name: 'biscuits',
    items: ['1', '2', '3', '4']
  },
];

const updatedCategories = categories.map((category) =>
  category.name === 'drinks'
    ? {
        ...category,
        items: category.items.filter((item) => !itemsToRemove.includes(item)),
      }
    : category,
);

console.log('updated', updatedCategories);
console.log('original', categories);

Upvotes: 1

Related Questions