Redux-persist: Immutable state merging issues with default reconciler

Created on 30 Oct 2017  路  9Comments  路  Source: rt2zz/redux-persist

Loving the v5 API btw! Have been having some issues using immutable/redux-persist-transform immutable and thought I would share the workaround I've used - maybe people will have better ideas.

The spread operators in persistReducer and the various reconciler methods end up spreading Immutable objects and thus turning them into native JS objects with no clear way to get them back. ie you get objects like { size: 123, __proto__ ...} etc. I wasn't able to work out how to resolve this when using persistReducer by itself, but when using persistCombineReducers I did something like:

import { persistCombineReducers } from 'redux-persist';
import immutableTransform from 'redux-persist-transform-immutable';
import { Map } from 'immutable';

const immutableReconciler = (inboundState, originalState, reducedState) => {
    const newState = { ...reducedState };

    Object.keys(reducedState).forEach(reducerKey => {
        if (reducerKey === '_persist' || originalState[reducerKey] !== reducedState[reducerKey]) {
            return;
        }
        newState[reducerKey] = Map.isMap(newState[reducerKey])
            ? newState[reducerKey].merge(inboundState[reducerKey])
            : { ...newState[reducerKey], ...inboundState[reducerKey] };
    });

    return newState;
};

const persistConfig = {
    ...
    transforms: [ ..., immutableTransform() ],
    stateReconciler: immutableReconciler
};

const reducer = persistCombineReducers(persistConfig, { ...reducers });

I know that this is in fact the issue pointed out in the docs that top-level state has to be a plain object - but it feels like there must be a way round it...

Most helpful comment

@Ewocker That issue is caused by the default state reconciler stripping the seamless-immutable methods.

Based on the input in this thread I built a quick lib that should let you use seamless-immutable with v5, with the same caveat that you cannot use an immutable at the top-level. Per-reducer is fine. It specifically fixes the bug you get with state.merge is not a function.

https://github.com/hilkeheremans/redux-persist-seamless-immutable

All 9 comments

I agree this is a pain right now, clever workaround though.

Its not a trivial problem and I am all ears for how to make this doable without workarounds. A few things we might consider:

  1. part of the "plain object" assumption is so that we can colocate _persist state in the state tree. however this is not strictly required, we could just as well store the persist state either in memory or in the persistor. This change could be made without any external api changes, but it would be fairly extensive and would need to be well teted.
  2. stateReconciler is pluggable so supporting immutable there should be no problem (as you have demonstrated)
  3. persistoid.update assumes the incoming state is an object so it can achieve a higher level of performance by breaking up work per sub-key. I am not sure how to elegantly workaround this. in v4 we simply made configurable the concepts of state getter / setter / iterator but this felt heavy and I am reluctant to reintroduce this pattern.

Certainly agreed that this isn't a trivial problem! Also agreed on your first 2 points - regarding 1., I do like the simplicity of colocating _persist (ie redux can do the work for us) but, as we can see, it does introduce a bit of coupling to the data structures used...

3 is tricky - I agree that providing a configurable interface between state and persistoid is a bit heavy although tbh wouldn't be totally averse to it. Have been mulling it over when I've had time over the past couple of days and an idea that I kind of like (albeit without being familiar with the codebase) is to make createPersistoid pluggable in some sense.

I then envisage some kind of redux-persist-immutable-plugin which provides the necessary reconcilers, serializers, persistoids and so on (and a utility function to insert them all at once, perhaps). It would be a shame to significantly modify the code here just to support Immutable - but on the other hand it would be a shame to couple the library to having to use native objects.

I faced the same problems last week when implementing my store with immutables and ended in re-implementing most of the redux-persist parts.
As spread operators and plain object initializers are spread all over the code maybe it's best to create an immutable branch or a fork?
I'd be happy to assist as needed whenever my daily business allows me to, just drop me a line.

Hope it's OK to mention it here...
As I wasn't happy with the mess I created when trying to "hack" redux-persist to support immutables all down the road I decided to create a new implementation as "store enhancer".
It's pre-pre-pre-alpha stage right now but as far as I could test right now it seems to work OK.
More docs will follow, I'll just add some "first steps" right now.

Here we go: https://github.com/actra-development-oss/redux-persistable

I am creating a react-native app now that is running v4.x successfully; however, we have not released it and I'd like to upgrade to v5.x before we do so we don't run into migration issues. We are not using immutable-js and are instead using seamless-immutable but I am running into similar issues with immutability.

On v4.x I was able to easily get it up and running using the stateReconciler to convert state to an immutable object. It is a little sad that it seems to be a step backwards (in this one specific regard) that this library is now so tied to the data type when it wasn't previously. From my experience it is pretty common for engineers to be using immutable objects when using redux for obvious reasons.

I wanted to upgrade so we don't end up with stale library in our code-base (v4.x) as well has having built in migration/version support per reducer which was a huge plus for the project.

Any further thoughts on the matter?

I was liking the v5.x API though! Per reducer config makes much more sense in my opinion and should make migration, data management, and version control easier.

Hi, I just start using redux-persist and I started off from ignite boilerplate.
They were using v4, which I change to use v5 instead now.
Everything works perfect except when Immutable Transform.
So basically when every time after rehydration, it does a ImmutableTransform and I verify that the object is transformed into a Immutable object yet when reducer update the state, it will say state.merge is not a function because it is not an Immutable object. I am not sure if this is the same problem as above because I am still kind of new to the community but is certain that this is at least a related problem and somehow after transformation the data is transformed back into a JS Object.

And Thanks 200% to the above work around, it saves up lots of my time. Looking forward to see the issue being resolved. (BTW, I am using seamless-immutable)

@Ewocker, that problem could be unrelated. Ignite doesn't come with the state fully immutable, only each substate (reducer) is Immutable. Check out redux-seamless-immutable. I'm not guaranteeing it will fix anything but I ran into a similar issue using ignite. If you have any questions about redux-seamless-immutable feel free to reach out to me since it is really unrelated to this bug. I don't want to clog up this issue.

@Ewocker That issue is caused by the default state reconciler stripping the seamless-immutable methods.

Based on the input in this thread I built a quick lib that should let you use seamless-immutable with v5, with the same caveat that you cannot use an immutable at the top-level. Per-reducer is fine. It specifically fixes the bug you get with state.merge is not a function.

https://github.com/hilkeheremans/redux-persist-seamless-immutable

Hi @here,

I got same issue with error: Unhandled Rejection (TypeError): state.merge is not a function when try apply redux-persist with seamless-immutable. Have any solution to resolved this issue don't use third-party like redux-persist-immutable-plugin???

Was this page helpful?
0 / 5 - 0 ratings