24

I'm using ngrx store.

In my state I have to items

export interface ISchedulesState {
  schedulings: ISchedules;
  actualTrips: ISchedule[];
}

Here are my interfaces

export interface ISchedules {
  [key: string]: ISchedule[];
}

export interface ISchedule {
  dest: number;
  data: string
}

In reducer I update actualTrips

export const SchedulingReducers = (
  state = initialSchedulingState,
  action: SchedulesAction
): ISchedulesState => {
  switch (action.type) {
    case ESchedulesActions.GetSchedulesByDate: {
      return {
        ...state
      };
    }
    case ESchedulesActions.GetSchedulesByDateSuccess: {
      return {
        ...state,
        schedulings: action.payload
      };
    }
    case ESchedulesActions.GetSchedulesByTime: {
      let time = action.payload;
      state.actualTrips = [...(state.schedulings[time] || [])]; // if not data return empty array
      return state;
    }
    default:
      return state;
  }
};

But actually I get an error

ERROR TypeError: Cannot assign to read only property 'actualTrips' of object '[object Object]'

infodev
  • 4,673
  • 17
  • 65
  • 138

5 Answers5

23

The basic principle of Redux pattern is immutability of state and its parts, because it let's us to detect changes just by object reference instead of comparing whole objects.

In your reducer, you cannot directly assign a property of state (state.actualTrips =), because change detector (and selectors) would not detect it as changed.

To modify state, you return a copy of the state with new modifications.

  const time = action.payload;
  return {
      ...state,
      actualTrips: [...(state.schedulings[time] || [])]
  }
kvetis
  • 6,682
  • 1
  • 28
  • 48
  • 4
    But the super annoying thing is that you want to modify for example a nested object (array in array in root object). As far as I can see, I have to do a deep-copy of the entire object tree and then update this in the store, which of course eats memory. How to avoid such a thing? – CularBytes Nov 10 '20 at 16:54
  • 1
    Well the whole point of the redux pattern change detection is that you can check only the object references instead of deep comparing the objects. So in order for it to work you need to combine old references with the new references. If a deeply nested object is changed so is the tree above it and all the way to the root state. Then the change detection works. You can simplify the operation using a nested reducer. – kvetis Nov 12 '20 at 09:47
  • How to avoid doing a deep-copy of the entire object tree to update the store? I recommend using an entity adapter to [normalize state shape](https://redux.js.org/usage/structuring-reducers/normalizing-state-shape). If you're using NgRx, look into @ngrx/entity. Rather than arrays, you would have an object with "byId" and "allIds". This flatter normalized state structure means updates key in on specific records rather than updating a whole array and results in simpler reducer logic. – David Rachwalik Sep 11 '22 at 09:32
  • I agree that having obejcts by id is a good idea. I'd like to correct a tiny mistake - in NGRX you are doing a shallow copy - this is a key concept of NGRX architecture. – kvetis Sep 12 '22 at 11:56
12

If you want to change state.actualTrips = myNewValue is not allowed because there is a strict Setting. So one way is may to clonedeep and return the object, like newState = cloneOfState... I didn't test it. So I changed the setting in app.module for Store. My Example: change the strictStateImmutability to false (full Docu here: https://ngrx.io/guide/store/configuration/runtime-checks )

    StoreModule.forRoot(ROOT_REDUCERS_TOKEN, {
        metaReducers,
        runtimeChecks: {
            // strictStateImmutability and strictActionImmutability are enabled by default
            strictStateSerializability: true,
            strictActionSerializability: true,
            strictActionWithinNgZone: true,
            strictActionTypeUniqueness: true,
            // if you want to change complexe objects and that we have. We need to disable these settings
            // change strictStateImmutability, strictActionImmutability
            strictStateImmutability: false, // set this to false
            strictActionImmutability: true,
        },
    }),
Dharman
  • 30,962
  • 25
  • 85
  • 135
  • I'm impressed that no one +1 your answer, thanks, worked for me. – Mansour Alnasser Apr 20 '21 at 13:03
  • You save my day, Thanks!!! – Daniel M Sánchez Sep 24 '21 at 03:38
  • You may not want to disable strictStateImmutability checks. This is there for a reason. When using immutable state properly, you can enable OnPush Change Detection. This will radically improve performance. Also you may lose the ability to use Time-Travel debugging. Removing this check removes a lot of the benefits of NgRx. Check out the NgRx Example App for examples. – Ryan Dec 17 '21 at 17:50
  • This didn't work for me in ngrx 13 in a testing environment. – griest Apr 14 '22 at 15:23
6

That error happened me when I changed the input values in the template. I was using Angular11 + NGRX11 so I understood I was changed a value from store, this was my fix:

Before:

 this.store.dispatch(new Actions.LoginUser({ user: this.user }));

After:

 const clone = { 
  user: Object.assign({}, this.user) 
 };
 this.store.dispatch(new Actions.LoginUser(clone));
CrgioPeca88
  • 973
  • 7
  • 12
0

I found the answer at https://stackoverflow.com/a/58279719

Simply copy the state into a new object

const oldState = getState();

let state = JSON.parse(JSON.stringify(oldState)); // here 

state.propertyToChange = "newValue";

patchState({
   ...state
});
Mo D Genesis
  • 5,187
  • 1
  • 21
  • 32
  • The only problem with JSON'ing the object is that you lose the type information - so dates and numbers become strings, which can be annoying of you need to re-cast back. Thanks for the post though :) – Malcolm Swaine Jun 22 '23 at 15:14
0

For me I needed the following syntax in my app.module

    StoreModule.forRoot(reducers, {
          runtimeChecks: {
            strictStateImmutability: false,
            strictActionImmutability: false,
          },
        }),

Credit to Dharman's post above...

Malcolm Swaine
  • 1,929
  • 24
  • 14