1

I have an array of objects in my react state.

[
  {
    id: 'd8t4gf',
    title: 'Working on',
    items: [{ title: 'Item 1' }, { title: 'Item 2' }, { title: 'Item 3' }],
  },
  {
    id: '8jy8g',
    title: 'Done',
    items: [{ title: 'Item 1' }, { title: 'Item 2' }],
  },
]

I'm trying to update the items of the second object like so.

const handleAddNewItemSubmit = (title, id) => {
  const listIndex = lists.findIndex((list) => list.id === id);

  const newData = [...lists];

  newData[listIndex].items = [...newData[listIndex].items, { title }];

  setLists(newData);
};

Is there a better way to do this?

2 Answers 2

2

You're mutating the existing state (specifically, an array object - eg lists[0]), which should never be done in React. What you're doing may happen to work, but it's not a good way.

Try something like this instead, cloning everything along the way down to the nested items property:

const handleAddNewItemSubmit = (title, id) => {
  setLists(
    lists.map(list => list.id !== id ? list : ({
      ...list,
      items: [...list.items, { title }]
    })
  );
};
Sign up to request clarification or add additional context in comments.

4 Comments

Looks like I have to learn more about react state management, can you suggest any resource where I should about it?
There isn't any direct mutation of array in the state, all complexe mutation are perform on the copy data const newData = [...lists];
@YvesKipondo The problem is the newData[listIndex].items =. newData is a shallow copy, not a deep copy. newData[listIndex] which gets mutated is a reference to a state object, not a new object.
@vajad57 "Avoid mutation" is pretty much the only hard and fast rule I know you absolutely should follow everywhere. Nothing else with regards to state management comes anywhere close in importance, I think
1
const handleAddNewItemSubmit = (title, id) => {
  const listIndex = lists.findIndex((list) => list.id === id);

  const newData = [...lists];

  newData[listIndex].items = [...newData[listIndex].items, { title }];

  setLists(newData);
};

In the above code, when you do const newData = [...lists] it just do shallow copy, i.e., only the top level references will be different but if there are any nested objects, they'll have same references as of lists.

So when yo do newData[listIndex].items you are actually referring to the same object i.e., lists[listIndex].items. Since newData[listIndex].items is an array which in turn is an object it'll have the same reference as of lists[listIndex].items.

So, ultimately you end up mutating the state. In order to avoid that, we can do the state update in different ways

const handleAddNewItemSubmit = (title, id) => {
  setLists(oldLists => oldLists.map(list =>
    list.id === id ? ({
      ...list,
      items: [...list.items, { title }]
    }) : list))
}

Below I have simulated two examples one which mutates top level object, another which mutates nested objects after spreading.

let lists = [
  {
    id: 'd8t4gf',
    title: 'Working on',
    items: [{ title: 'Item 1' }, { title: 'Item 2' }, { title: 'Item 3' }],
  },
  {
    id: '8jy8g',
    title: 'Done',
    items: [{ title: 'Item 1' }, { title: 'Item 2' }],
  },
];

const addNewTitleOnTopLevel = (title) => {
  const newData = [...lists];
  newData.push({id: '123', title});
  
  //Since doing `[...lists]` will do a shallow copy, the lengths will be different;
  console.log(newData.length, lists.length);
  //The objects are different so, it'll be false.
  console.log(newData === lists);
}

const addNewTitleInnerItems = (title, id) => {  
  const listIndex = lists.findIndex((list) => list.id === id);  
  const newData = [...lists];
  newData[listIndex].items = [newData[listIndex].items, {title}]
  
  //Since `newData[listIndex].items` & `lists[listIndex].items` 
  //are pointing to same references, their length will be same
  console.log(newData[listIndex].items.length, lists[listIndex].items.length);
  //To know if they both are same or not
    console.log(newData[listIndex].items === lists[listIndex].items);
}

addNewTitleOnTopLevel("test");
addNewTitleInnerItems("test", '8jy8g');

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.