4
votes

I have an array of objects currently in my store. I want to update a single property of that object given a passed in object.

State

export interface ApplicationState {
  ...
  allNavGroups: INavGroup[] | null;
}

Reducer

  on(ApplicationActions.toggleCollapseNavGroup, (state, group: INavGroup ) => {
    const navGroup = state.allNavGroups.find( g => g.groupId === group.groupId);
    navGroup.collapsed = !navGroup.collapsed;
    return { ...state };
  })

However, this results in:

ERROR TypeError: Cannot add property collapsed, object is not extensible

After seeing this GitHub issue, I tried doing it this way instead, but same issue:

const navGroups = [ ...state.allNavGroups ];
const navGroup = navGroups.find( g => g.groupId === group.groupId);
navGroup.collapsed = !navGroup.collapsed;
return { ...state, allNavGroups: navGroups };

I have seen a few threads on different ways to update an object in an array, but it seems like a lot of code to do a simple thing. In other threads, I saw that you have to replace the object, not update it. When I tried that, it seemed to work. However, that requires more logic to be added to make sure the order of the array is the same as it once was.

Is there a simple way to update just a property on an object within an array of objects that is in the Store?

Update If I iterate through each object in the array and destructure it, it works. This seems like a hack.

const navGroups = state.allNavGroups.map(g => ({ ...g }));
const navGroup = navGroups.find( g => g.groupId === group.groupId);
navGroup.collapsed = !navGroup.collapsed;
return { ...state, allNavGroups: navGroups };
1
your update was very helpful. Thanks!omostan

1 Answers

9
votes

From my understanding, you have to mutate the state immutably for properties that are stored by reference (arrays and objects) even if they are nested inside each other.

So for your case, I would do:

  return {
    ...state,                        // 1
    allNavGroups: state.allNavGroups.map(navGroup => ({...navGroup})) // 2
                                    .map(navGroup => { // 3
                                        if (navGroup.groupId === group.groupId) {
                                          return {
                                            ...navGroup,
                                            collapsed: !navGroup.collapsed,
                                          } else {
                                           return navGroup;
                                          }
                                        }
                                     })
  };

1 - the map creates a new array (different location in ram), so we are taking care of updating the array in immutable way.

2 - we are spreading the object and creating a copy (different location in ram), so we are taking care of updating the inner object in an immutable way.

3 - we are creating a new array with the 2nd map and changing one of the object's keys. It might not be necessary to spread navGroup and tack on the collapsed property but we did it nonetheless. This could be something for you to experiment with.

As to why it is done this way, I believe it helps with performance and helps with time travel debugging. If you were to change this property of an object in the array later in the application in a mutable way, going through the redux store in the devtools will make it as if all previous states had this change which is inaccurate (memory leak).