I'm trying implement nested persists so that I can modify the state as it gets rehydrated. I have tried to follow the example in the README. Below is my code:
import * as localForage from 'localforage';
import { combineReducers } from 'redux';
import { createTransform, persistReducer } from 'redux-persist';
import surveyAnswers, { State as SurveysAnswersState } from './surveyAnswers';
import { default as surveysReducer, State as SurveysState } from './surveys';
export type AppState = {
surveys: SurveysState,
surveyAnswers: SurveysAnswersState,
};
const surveysPersistTransforms = createTransform(
// transform state coming from redux on its way to being serialized and stored
(state: SurveysState) => {
return state;
},
// transform state coming from storage, on its way to be rehydrated into redux
(state: any, key: string) => {
if (key === 'surveys') {
// ... modify and return state
}
return state;
},
);
const surveysPersistConfig = {
key: 'surveys',
storage: localForage,
transforms: [
surveysPersistTransforms,
],
};
const surveyAnswersPersistConfig = {
key: 'surveyAnswers',
storage: localForage,
};
export default combineReducers({
surveys: persistReducer(surveysPersistConfig, surveysReducer),
surveyAnswers: persistReducer(surveyAnswersPersistConfig, surveyAnswers),
});
However, when I dispatch more than 1 action and refresh the page I can see that it has only persisted the first change is persisted. If I update my code to the following it will persist all changes, but I lose my manipulation during rehydration :(
import * as localForage from 'localforage';
import { combineReducers } from 'redux';
import { persistReducer } from 'redux-persist';
import surveyAnswers, { State as SurveysAnswersState } from './surveyAnswers';
import { default as surveysReducer, State as SurveysState } from './surveys';
export type AppState = {
surveys: SurveysState,
surveyAnswers: SurveysAnswersState,
};
const combinedReducers = combineReducers<AppState>({
surveys: surveysReducer,
surveyAnswers,
});
const config = {
key: 'app',
storage: localForage,
};
export default persistReducer(config, combinedReducers);
I create my store as such:
import { applyMiddleware, createStore, Store } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistStore } from 'redux-persist';
import reduxThunk from 'redux-thunk';
import rootReducer, { AppState } from '../reducers';
export function configureStore(initialState?: AppState): { persistor: any, store: Store<AppState> } {
const middleware = [reduxThunk];
const store = createStore(rootReducer, initialState, composeWithDevTools(
applyMiddleware(...middleware),
)) as Store<AppState>;
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers');
store.replaceReducer(nextReducer);
});
}
const persistor = persistStore(store);
return { persistor, store };
}
Am I doing something wrong or it this a bug?
I'm having the same issue, without any custom transformations:
let persist = (key: string, reducer: Reducer<any>) => persistReducer({
key,
storage,
blacklist: ['actionHistory']
}, reducer)
const reducer = persistCombineReducers({
key: 'root',
storage,
blacklist: ['foo', 'bar']
}, {
router: routerReducer,
foo: persist('foo', fooReducer),
bar: persist('bar', barReducer),
})
This is concerning, I will look into it.
@micimize you do not need to call persistCombineReducers and persistReducer - one or the other. I recommend keep the exact same code and simply remove persistCombineReducers
@JosephDuffy so I wrote a little test for this: https://github.com/rt2zz/redux-persist/pull/711
But it passes. I am not sure what might be going on, can you reproduce this either with a test or a demo project that I can clone?
@rt2zz figured out that Object.assign is the culprit in my code, probably the same for @JosephDuffy , given his transform returns the same state object, but mutated. It seems that changing an object without replacing it results at any level of the state results in redux-persist not picking up the change.
I made the test in #711 fail by changing it to incorporate mutation
let reducer = (state = { counter: 0 }, action) => Object.assign(state, {
counter: action.type === INCREMENT ? state.counter + 1 : state.counter,
})
ah got it. So redux persist relies on an assumption of immutability in a few places, as does the greater redux ecosystem to a large extent. I am not sure we can do anything about this other than better communicate this in the docs.
The comment above is correct -- I must have been mutating the object directly somewhere. I have since moved to Immutable.js and rechecked this issue to find it is no longer present, so it was not an issue with redux-persist!
Thanks, @micimize, you saved my day.
I changed my Object.assign(state, payload) to {...state, ...payload} and it solved the issue.
Most helpful comment
@rt2zz figured out that
Object.assignis the culprit in my code, probably the same for @JosephDuffy , given his transform returns the same state object, but mutated. It seems that changing an object without replacing it results at any level of the state results inredux-persistnot picking up the change.I made the test in #711 fail by changing it to incorporate mutation