Redux-persist: V5 everything under single local storage key

Created on 16 Oct 2017  路  5Comments  路  Source: rt2zz/redux-persist

It seems that redux-persist-transform-filter now store everything under single key.

const entitiesSubsetFilter = createFilter(
  'entities',
  ['deliveries']
);

const config = {
  storage,
  key: 'tracker',
  whitelist: ['entities'],
  transforms: [entitiesSubsetFilter]
};

Whole state is persisted in localDb under single key: persist:tracker = { entities: { deliveries: {} }}

In V4 separate key was created for every reducer:

reduxPersist:entities = { deliveries: {} }, and so on.

I think V4 is more performant as only slice of state will be serialised on state change (V4) vs serialising whole state atom (V5).

image

On screenshot V5 key is highlighted. Other keys are from V4

EDIT:

Also as seen on screenshot {"entities":"{\"deliveries\":{}}"} are unnecessarily double escaped.
It seems that redux-persist is doing serialisation by calling JSON.stringify twice. This is also new in V5 (in V4 it was ok).

V5 seems to be doing

JSON.stringify({ entities: JSON.stringify({ deliveries: {} }) });

V4 seems to be doing

JSON.stringify({ entities: { deliveries: {} } });

which will persist key's value as: {"entities":{"deliveries":{}}}

Most helpful comment

in v5 this would work:

let rootReducer = combineReducer({
  a: persistReducer(persistConfigA, a),
  b: persistReducer(persistConfigB, b),
})

so e.g. you might persist one set of state to sessionStorage and another to localForage. Or you might configure special rules in one to expire stored state on rehydration etc.

All 5 comments

@mauron85 thanks for the feedback. Ill explain the logic behind it, but yes there is probably room for further optimization, I am very open to new ideas on the matter.

Why v5 works like it does
By using just 1 key, we get a couple of big benefits:

  1. no longer need getAllKeys in storage adapter. This means rehydrate is faster, and makes the lib more compatible with storage libraries that lack a decent version of this method.
  2. state is stored all at once so you can never end up with inconsistent state atoms (e.g. if there is an implicit dependency between two sub reducers, it could be problematic if one updated and the other fails or is interrupted.

    1. v5 supports nested persisted reducers. This means we can no longer reasonably assume we are persisting the top level combineReducers reducer - and in turn for handing this power back to the implementor, it is less clear how to automatically break up the persistence.

Performance:
The double stringify comes from a performance "optimization" we have, which is that we serialize each key separately over time. Stringify is typically the most costly part of persistence (and is all on the main thread) so we want to make sure to break that up as much as possible. Saving to disk however is typically on another thread and async so, saving a giant combined value all at once is not so scary.

The double stringify then comes from the fact that we have a bunch of sub-states serialized, that we need to combine and serialize again before we can save to disk. I have done some very basic benchmarking - JSON.stringify of a stringified object is 100% - 150% faster than JSON.stringify of the original object. IMO worth the the tradeoffs.

That said there is probably room for further optimization. e.g. since we know we are always storing an object of strings at the top level, perhaps there is a more compact and custom way to do the final serialization that does not require escaping the quotes in the sub state strings.

Ok, it make sense to me now. Specially point 2. about state inconsistency. Also I think double stringify is not problem for now (but maybe can be improved like you said).

What I don't understand is point 3. - nested persisted reducers. Can you give me some example what are those?

in v5 this would work:

let rootReducer = combineReducer({
  a: persistReducer(persistConfigA, a),
  b: persistReducer(persistConfigB, b),
})

so e.g. you might persist one set of state to sessionStorage and another to localForage. Or you might configure special rules in one to expire stored state on rehydration etc.

That is actually pretty cool. In my react-native project I actually need to use two stores. One for small data and another one for quite large data.

in v5 this would work:

let rootReducer = combineReducer({
a: persistReducer(persistConfigA, a),
b: persistReducer(persistConfigB, b),
})

@rt2zz Can you plz tell me how to do this

Was this page helpful?
0 / 5 - 0 ratings