Ngrx : Cannot assign to read only property 'Property' of object '[Object]'
Asked Answered
U

6

25

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]'

Unskilled answered 21/8, 2019 at 11:51 Comment(0)
H
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] || [])]
  }
Hardheaded answered 21/8, 2019 at 12:4 Comment(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?Cryotherapy
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.Hardheaded
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. 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.Jeaniejeanine
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.Hardheaded
A
14

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,
        },
    }),
Ambulate answered 20/4, 2021 at 11:51 Comment(4)
I'm impressed that no one +1 your answer, thanks, worked for me.Askins
You save my day, Thanks!!!Darrelldarrelle
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.Menke
This didn't work for me in ngrx 13 in a testing environment.Phaidra
C
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));
Chloropicrin answered 27/2, 2021 at 18:51 Comment(0)
T
1

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...

Tormoria answered 22/6, 2023 at 15:12 Comment(0)
F
1

If you have an array of objects you may also want to install the lodash library which allows you to do a deep cloning.

npm i --save lodash
npm install --save-dev @types/lodash

then in your component:

import * as _ from 'lodash';

and use it:

cloneObject = _.cloneDeep('your object')
Franky answered 6/10, 2023 at 11:27 Comment(0)
R
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
});
Resolutive answered 29/6, 2022 at 13:17 Comment(1)
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 :)Tormoria

© 2022 - 2024 — McMap. All rights reserved.