Search code examples
angulartypescriptreduxngxs

How to delete/remove an item from normalized state?


I have the following state structure:

{ entities:
1: {name: "Basketball", id: "1", leagues: Array(3)}
2: {name: "Volleyball", id: "2", leagues: Array(3)}
3: {name: "Soccer", id: "3", leagues: Array(0)}
}

Now I just want to remove an item with the id '3' lets say.

The following does not work:

const state = ctx.getState();
    delete state.entities[action.id];

    ctx.setState(
      patch<SportTypeStateModel>({
        entities: {...state.entities},
        IDs: state.IDs.filter(id => id !== action.id)
      })
    );

It throws the following error:

`ERROR TypeError: Cannot delete property '3' of [object Object]`

What is the right way of doing this?


Solution

  • First we need to understand why this error occurs.

    NGXS uses deepFreeze under the hood in the development mode to Object.freeze your state (and deeply nested objects/arrays) to prevent unpredictable mutations.

    You can check it up by calling Object.isFrozen:

    const state = ctx.getState();
    console.log(Object.isFrozen(state.entities));
    delete state.entities[action.id];
    

    I understood your point that entities is not an array, but an object.

    So the problem is once an object has been frozen there is no way to unfreeze it. What do we have to do? We have to unfreeze the state object itself, the entities object and its children:

    const state = ctx.getState();
    const newState = { ...state, entities: { ...state.entities }};
    for (const key of Object.keys(newState.entities)) {
      newState.entities[key] = { ...newState.entities[key] };
    }
    console.log(Object.isFrozen(newState.entities));
    delete newState.entities[action.id];
    

    I do not like this code so don't throw stones at me :) I think you can search for some packages like deep-unfreeze to be more declarative. Oh, I forgot about the IDs property. The final code is:

    ctx.setState(state => {
      const newState = {
        entities: { ...state.entities },
        IDs: state.IDs.filter(id => id !== action.id)
      };
      for (const key of Object.keys(newState.entities)) {
        newState.entities[key] = { ...newState.entities[key] };
      }
      delete newState.entities[action.id];
      return newState;
    });
    

    P.S. checked it locally.