mozpider
mozpider

Reputation: 364

Why is it considered a direct modification of store values in NgRx

Info about the snippet: state has a slice educationList which is an array of various educations (BS, MS, PhD). Each item in educationList array contains another array description, which contains info for projects, thesis etc.

The following is the snippet for deleting one of items of the description array:

case Actions.DELETE_ROLE: {
      let updatedEducationList = [...state.educationList];
      const targetIndex = updatedEducationList.findIndex((educationItem) => {
        return educationItem.id === action.payload.educationId;
      });
      let oldDescription = updatedEducationList[targetIndex].description;
      let newDescription = oldDescription.filter((item, index) => index !== action.payload.roleIndex);
      updatedEducationList[targetIndex] = { ...updatedEducationList[targetIndex], description: newDescription };
      return { ...state, educationList: updatedEducationList };
    }

if the following line in the snippet,

updatedEducationList[targetIndex] = { ...updatedEducationList[targetIndex], description: newDescription }

is replaced with

updatedEducationList[targetIndex].description = newDescription;  //Edited this line

error takes place. The error is following.

core.js:6014 ERROR TypeError: Cannot assign to read only property 'description' of object '[object Object]'
    at educationListReducer (education-list.reducer.ts:110)
    at combination (store.js:303)
    at store.js:1213
    at store.js:383
    at ScanSubscriber.reduceState [as accumulator] (store.js:688)
    at ScanSubscriber._tryNext (scan.js:49)
    at ScanSubscriber._next (scan.js:42)
    at ScanSubscriber.next (Subscriber.js:49)
    at WithLatestFromSubscriber._next (withLatestFrom.js:57)
    at WithLatestFromSubscriber.next (Subscriber.js:49)

But I think I already copied the state in the 1st line itself.

let updatedEducationList = [...state.educationList];

What am I missing here?

Upvotes: 1

Views: 82

Answers (1)

satanTime
satanTime

Reputation: 13539

let updatedEducationList = [...state.educationList];
// now updatedEducationList is a new array
// but its every element points to the related value in the current state.
// because it is not a deep clone.

const targetIndex = updatedEducationList.findIndex((educationItem) => {
    return educationItem.id === action.payload.educationId;
});
// now if you check state.educationList[targetIndex] === updatedEducationList[targetIndex] it will be true.
// Despite updatedEducationList === state.educationList is false.

let oldDescription = updatedEducationList[targetIndex].description;
// now it points to the current state.

let newDescription = oldDescription.filter((item, index) => index !== action.payload.roleIndex);
// a new array of pointers to the current state items.

updatedEducationList[targetIndex].description = newDescription;
// wrong, because updatedEducationList[targetIndex] still points to the current state. and so does description.

updatedEducationList[targetIndex] = { ...updatedEducationList[targetIndex], description: newDescription };
// right, because it creates new object that doesn't point to current state anymore. only values of its keys.

return { ...state, educationList: updatedEducationList };

ANSWER

For objects and arrays containing other objects or arrays, copying these objects requires a deep copy. Otherwise, changes made to the nested references will change the data nested in the original object or array. In this case description is a nested object, therefore after the shallow copy, it still points to the same address in the memory as that from the original state

Upvotes: 1

Related Questions