-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Fix for state mutation when rehydrating state #113
Conversation
I wanted to add ngrx-store-freeze as a test to prove it fails without this change and passes with it. However I get this rather unexpected error.
|
Testing this in my own project, it appears ineffective. I still get the
|
I actaully tried to add another test for filtered (selective) states, based on my specific use case, but I didn't really know how to reliably test it. By reliably I mean - mimicking a real ngrx app behavior: rehydrate, then run a reducer along with this metareducer. This test case for filtered states is different from the one already included, in that user designates incomplete feature slices, and in that the cherry-picked subslices (
|
With regards to immutability, I think it is the returned meta reducer that needs it, I have been trying something like:
It might be over-using spread operator for now (not sure |
Yeah the problem is doing this It should be really easy to add the freeze meta reducer to the test but I get that |
@bufke, this assignment |
@moniuch isn't it that only state[key] = value violates immutability? state = value shouldn't, since we get a new state, not mutating the existing; i have experimented and have a working branch tested with store-freeze and local tests; however whats troubling is that rehydration doesn't deep-merge and if it would reconciliation is troublesome; ex: const initial = { a: { b: { Foo: {}, C: [] } } }
const hydrated = { a: { b: { Bar: {}, C: {} } } }
const next = { a: { b: { /* Foo or Bar or Both: {}? also how we deal with different types of C */ } } } |
@victor-ca yes, you are right, I overlooked that. As per deep merge, I totally agree - and that is why in #107 I commented on the code using lodash merge as a quick tool for experiment purposes. Basically I can identify the following problems we have currently and they get mixed around in our discussions:
|
@moniuch yep, yep regarding 2: maybe we should leave the issue to library users :) (it can be tackled with custom deserializers); |
How can 2 be solved if we effectively distort the state, so to speak? |
Updating other packages fixes the freeze issue I had before. Check out the only slightly updated test - it reproduces the mutation issue. Not that I'm against other changes to the tests :) I'd be fine with some incrementalism here. If we fix the specific mutation issue without adding an external package that should solve the original merge state issue and the new freeze issue. I think it's reasonable to expect custom deserializers at some point, but basic usage should "just work". |
This fixes the unit test
We need more test cases though. I need to head out for a bit, should be around again in maybe 5 hours or so. |
I had to fix the code to make it work at least with my setup so I cloned the original function and worked on the code. As I am not able to come up with a proper PR right now, I am sending you just a small portion (just the returned function) to let you play with it, perhaps run the test. My code basically:
Note: as I use Please play with this code, see if you see any problems. If the comments are positive. I will come up with PR later in the evening European time. index.ts import * as merge from 'lodash/merge';
// ...unchanged code - snipped for brevity...
return function (state, action: any) {
let nextState;
// If state arrives undefined, we need to let it through the supplied reducer
// in order to get a complete state as defined by user
if ((action.type === INIT_ACTION) && !state) {
nextState = reducer(state, action);
} else {
nextState = { ...state };
}
if ((action.type === INIT_ACTION || action.type === UPDATE_ACTION) && rehydratedState) {
nextState = merge({}, nextState, rehydratedState);
}
nextState = reducer(nextState, action);
if (action.type !== INIT_ACTION) {
syncStateUpdate(
nextState,
stateKeys,
config.storage,
config.storageKeySerializer,
config.removeOnUndefined,
config.syncCondition,
);
}
return nextState;
}; My usage, just in case: export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
return myLocalStorageSync({
keys: [{ 'app': ['language'] }, { 'cart': ['tokens'] }],
rehydrate: true,
})(reducer);
} |
Closing in favor of #114 |
What does this solve?
Follow up for #107
We need to not mutate state when merging localstorage hydrated state and new initial state. This is bad in principle and breaks outright for users of ngrx-store-freeze.
How
Open issues/questions