We had the following ngrx reducer, but added a nested object to our state (structure: settings: { a: boolean, b: string }
), so decided to use lodash’s cloneDeep(obj)
instead of the ...obj
spread operator to ensure our state is immutable, as per the second note in the official docs’ “Creating the Reducer Function” section:
The spread operator only does shallow copying and does not handle deeply nested objects. You need to copy each level in the object to ensure immutability. There are libraries that handle deep copying including lodash and immer.
// before:
export function reducer(state: MyState = initialState, action: Actions): MyState {
switch (action.type) {
// ...
case MY_TASK: {
return {
...state,
prop1: action.payload.prop,
prop2: initialState.anotherProp
};
}
// ..
}
}
When we changed that to the following, ngrx kept calling the reducer()
method, effectively blocking (crashing) the browser:
// after - causes infinite loop
import * as _ from 'lodash';
// ..
export function reducer(oldState: MyState = initialState, action: Actions): MyState {
function nextState(stateChanges?: (MyState) => void): MyState {
const draftState: MyState = _.cloneDeep(oldState);
if (stateChanges) {
stateChanges(draftState);
}
return draftState;
}
switch (action.type) {
// ...
case MY_TASK: {
return nextState(s => {
s.prop1 = action.payload.prop;
s.prop2 = initialState.anotherProp;
});
}
// ..
}
}
stateChange
doesn't even touch the new nested additional state properties (i.e. the settings
object), only the flat ones (prop1, prop2
) we had previously. settings
is, however, part of oldState
and thus passed to lodash's cloneDeep()
.
Any idea why changing the returned state (i.e. using _.deepClone(state)
instead of the spread ...state
operator) would have that effect - and how to stop it?
We are using @ngrx/entity
, so the state is extending EntityState<MyState>
. I unserstand that the library's entity functinos return a new state (or "the same state if no changes were made"), and that additional properties like our new nested settings
object can be added to the state but that
"[t]hese properties must be updated manually" - which I take to mean I have to clone them myself.
Is the mistake that we attempt to clone the entire state (which extends EntityState<T>
), not just the additional properties?
The only changes between the working and the broken code were made in the reducer()
method, so I don't think we are accidentally firing any new event we didn't fire before, which would usually associated with causing an infinite loop...