redux-persist-immutable
redux-persist-immutable copied to clipboard
Upgrade lib to support redux-persist v5
implemented support for persistReducer and persistCombineReducers from redux-persist lib.
This is my first pr to opensource project, you can explode into splinters persistReducer from redux-persist returns objects, in this PR persistReducer returns immutable instance
@OneStromberg I tried to use your version of library. I ended with console error and not rendered app. Maybe my implementation is not correct.
My intention is to persist selected values from selected nested reducers while keeping immutability of top layer and nested ones
Error:
Uncaught (in promise) Error: Reducer "authentication" returned undefined when handling "persist/REHYDRATE" action. To ignore an action, you must explicitly return the previous state
Nested reducer:
import {persistReducer} from 'redux-persist-immutable';
import storage from 'redux-persist/lib/storage';
import * as constants from './constants';
import {fromJS} from 'immutable';
const initialState = fromJS({
isAuthed: false,
isFetching: false,
apiToken: 'apiToken',
user: {},
test: '1'
});
function reducer(state = initialState, action) {
switch (action.type) {
case constants.APP_INIT:
return state;
default:
return state;
}
}
const persistConfig = {
key: 'authentication',
storage: storage,
whitelist: ['apiToken', 'isAuthed', 'test']
};
export default persistReducer(persistConfig, reducer)
Root reducer and store:
...
import storage from 'redux-persist/lib/storage'
import {persistCombineReducers} from 'redux-persist-immutable'
import authenticationReducer from './authentication/reducer.js'
import {applyMiddleware, compose,createStore} from 'redux'
...
const persistConfig = {
key: 'root',
storage: storage,
blacklist: ['authentication']
};
const persistedRootReducer = persistCombineReducers(persistConfig, {
authentication: authenticationReducer,
});
export const store = createStore(
persistedRootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware, sagaMiddleware))
);
...
export const persistor = persistStore(store);
Components usage:
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppContainer/>
</PersistGate>
</Provider>
@lukasikora hi there! Let's figure out the problem! Just to clarify what you try to achieve - you added authentication reducer to blacklist, and then you try to persist it; Looks like your authentication reducer is root one, so try to use it directly in createStore
export const store = createStore( authenticationReducer, composeEnhancers(applyMiddleware(thunkMiddleware, sagaMiddleware)) );
@OneStromberg Let's assume that I would like to use multiple nested reducers. Authentication reducer is one of them. I want to have top store object containing substores {auth: {}, navigation: {}, other: {}} and substores itself (like auth) to be immutable instances. . According to redux-persist doc if you want to define persisting of specific values in nested reducers you can achieve this by https://github.com/rt2zz/redux-persist#nested-persists. I'm looking for solution for nested reducers pattern (from link before) with providing immutability for whole store.
Similar case for defining nested persisting reducers and combining them (with blacklisting) would be if I would like to persist parts of store with different persisting engines(localStorage, session, custom db)
I tried once more to achieve immutability and store persisting from beginning step by step. I disabled immutability and persisting on top layer so instead 'persistCombineReducers' I used now 'combineReducer' from 'redux'.
now root reducer:
import authenticationReducer from './authentication/reducer.js'
import {applyMiddleware, compose, createStore, combineReducers} from 'redux'
import createSagaMiddleware from 'redux-saga';
import {persistStore} from 'redux-persist';
import {all} from 'redux-saga/effects';
import {composeWithDevTools} from 'remote-redux-devtools';
import thunkMiddleware from 'redux-thunk';
import authSagas from './authentication/sagas'
const composeEnhancers =
process.env.NODE_ENV === "development"
? composeWithDevTools : compose;
const sagaMiddleware = createSagaMiddleware();
const rootReducer = combineReducers({
authentication: authenticationReducer,
...otherReducers
});
export const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware, sagaMiddleware))
);
function* rootSaga() {
yield all([...authSagas])
}
sagaMiddleware.run(rootSaga);
export const persistor = persistStore(store);
authentication reducer:
import {persistReducer} from 'redux-persist-immutable';
import storage from 'redux-persist/lib/storage';
import * as constants from './constants';
import {fromJS} from "immutable";
const initialState = fromJS({
isAuthed: false,
isFetching: false,
apiToken: 'apiToken',
user: {},
test: '1'
});
function reducer(state = initialState, action) {
switch (action.type) {
case constants.APP_INIT:
return state.set('apiToken',Math.random().toString());
default:
return state;
}
}
const persistConfig = {
key: 'authentication',
storage,
whitelist: ['apiToken', 'isAuthed', 'test']
};
export default persistReducer(persistConfig, reducer)
App launched with no errors but I noticed 2 issues:
- auth reducer persisted fields that are not in whitelist, without fields from whitelist
after first app render localStorage looked like:
{"data":{"isFetching":"\"false\"","user":"\"{\\\"data\\\":{},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\"","_persist":"\"{\\\"data\\\":{\\\"version\\\":-1,\\\"rehydrated\\\":true},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\""},"__serializedType__":"ImmutableMap"}
- considering first issue I removed whitelist for further debugging and then I noticed that with next page refreshes localStorage looked like over-stringified.
after initial app rendering (without localStorage already set)
{"data":{"isAuthed":"\"false\"","isFetching":"\"false\"","apiToken":"\"\\\"0.442227839817376\\\"\"","user":"\"{\\\"data\\\":{},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\"","test":"\"\\\"1\\\"\"","_persist":"\"{\\\"data\\\":{\\\"version\\\":-1,\\\"rehydrated\\\":true},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\""},"__serializedType__":"ImmutableMap"}
first refresh:
{"data":{"isAuthed":"\"\\\"false\\\"\"","isFetching":"\"\\\"false\\\"\"","apiToken":"\"\\\"0.1672824424113064\\\"\"","user":"\"\\\"{\\\\\\\"data\\\\\\\":{},\\\\\\\"__serializedType__\\\\\\\":\\\\\\\"ImmutableMap\\\\\\\"}\\\"\"","test":"\"\\\"\\\\\\\"1\\\\\\\"\\\"\"","_persist":"\"{\\\"data\\\":{\\\"version\\\":-1,\\\"rehydrated\\\":true},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\""},"__serializedType__":"ImmutableMap"}
second refresh:
{"data":{"isAuthed":"\"\\\"\\\\\\\"false\\\\\\\"\\\"\"","isFetching":"\"\\\"\\\\\\\"false\\\\\\\"\\\"\"","apiToken":"\"\\\"0.6592952193837278\\\"\"","user":"\"\\\"\\\\\\\"{\\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\\":{},\\\\\\\\\\\\\\\"__serializedType__\\\\\\\\\\\\\\\":\\\\\\\\\\\\\\\"ImmutableMap\\\\\\\\\\\\\\\"}\\\\\\\"\\\"\"","test":"\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"1\\\\\\\\\\\\\\\"\\\\\\\"\\\"\"","_persist":"\"{\\\"data\\\":{\\\"version\\\":-1,\\\"rehydrated\\\":true},\\\"__serializedType__\\\":\\\"ImmutableMap\\\"}\""},"__serializedType__":"ImmutableMap"}
This implies incorrect store state after rehydration (all values are strings).
I've also checked case with second reducer.
Navigation reducer:
import {persistReducer} from 'redux-persist-immutable';
import storage from 'redux-persist/lib/storage';
import * as constants from './constants';
import {fromJS} from "immutable";
const initialState = fromJS({
screen: 'home'
});
function reducer(state = initialState, action) {
switch (action.type) {
case constants.NAV_INIT:
return state.set('screen', Math.random().toString());
default:
return state;
}
}
const persistConfig = {
key: 'navigation',
storage
};
export default persistReducer(persistConfig, reducer)
Combine :
const rootReducer = combineReducers({
authentication: authenticationReducer,
navigation: navigationReducer,
});
Now app throws errors while rendering.
Uncaught Error: Given action "persist/REHYDRATE", reducer "authentication" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.
Uncaught Error: Given action "persist/REHYDRATE", reducer "navigation" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.
Dose this version of library supports multi persisting reducers (like redux-presist) ?
Furthermore, it would be great to persist state in simple js object without immutable structures metadata (immutable toJS before persisting). It will allow to provide usage context inside application and use data despite immutability on/off. Reducer version (immutable/non-immutable) should decide how to parse data and add context for further processing inside app. And of course save data with minimum overhead. String length is crucial for React Native async storage.