michaelcontento / redux-storage

Persistence layer for redux with flexible backends
MIT License
675 stars 50 forks source link

Data in Local/Session storage resets to initialState on refresh #136

Closed mwq27 closed 8 years ago

mwq27 commented 8 years ago

I've tried redux-storage-engine-sessionstorage and redux-storage-engine-localstorage, and at first I see data saving correctly. Then when I refresh the page, all of that data is gone, and the data in storage is everything that is set as initialState in my reducers.

Anyone seen this before?

import createEngine from 'redux-storage-engine-sessionstorage';
import immutablejs from 'redux-storage-decorator-immutablejs';
import merger from 'redux-storage-merger-immutablejs';

let engine = createEngine('enjoymint');
engine = immutablejs(engine, [
  ['UserRed'],
  ['routing'],
  ['ErrorsRed'],
  ['MenuRed'],
  ['ModalsRed'],
  ['OrdersRed'],
  ['ProfileRed'],
  ['VendorRed']
]);

const finalReducers = storage.reducer(combineReducers(Object.assign({}, reducers, {
  routing: immutableRoutingReducer
})), merger);

const engineMiddleware = storage.createMiddleware(engine, [
  Constants.modals.OPEN_LOGIN_MODAL,
  Constants.modals.TOGGLE_ACCOUNT_DETAILS_MODAL,
  Constants.modals.TOGGLE_CC_MODAL,
  Constants.modals.TOGGLE_BILLING_MODAL,
]);

const routingMiddleware = routerMiddleware(createHistory);

const finalCreateStore = compose(
  applyMiddleware(thunk /*,logger*/, engineMiddleware, routingMiddleware),
  window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore);

const store = finalCreateStore(finalReducers);

const load = storage.createLoader(engine);
load(store)
  .then((newState) => console.log('Loaded state:', newState))
  .catch(() => console.log('Failed to load previous state'));
matt-casey commented 8 years ago

It could be the actions from your routing (I see you have a routing reducer, so it may be the case). I had the same thing happen using both redux-router and react-router-redux.

Basically, if a routing action fires before loading, save will get called before load and you'll be stuck with initial state.

One quick solution is to blacklist all of the router actions in the engine, like you have for your modal actions.

mwq27 commented 8 years ago

Thanks @matt-casey, I added the LOCATION_CHANGE action to the Blacklist, and that stopped the initialState error. However now when I change states, it's not saving with the rest of the application state, meaning when I start navigating around the app, then refresh, it takes me back to that first state. Here's the scenario: I go to home page, I log in and am taken to /account-info. I click on a link to /page1, then click a link to /page2. I hit refresh, and i'm brought back to /account-info.

mwq27 commented 8 years ago

UPDATE:

Ok I think I've got a working solution.

import createEngine from 'redux-storage-engine-sessionstorage';
import immutablejs from 'redux-storage-decorator-immutablejs';
import merger from 'redux-storage-merger-immutablejs';

let engine = createEngine('enjoymint');
engine = immutablejs(engine, [
  ['UserRed'],
  ['routing'],
  ['ErrorsRed'],
  ['MenuRed'],
  ['ModalsRed'],
  ['OrdersRed'],
  ['ProfileRed'],
  ['VendorRed']
]);

const finalReducers = storage.reducer(combineReducers(Object.assign({}, reducers, {
  routing: immutableRoutingReducer
})), merger);

const engineMiddleware = storage.createMiddleware(engine, [
  Constants.modals.OPEN_LOGIN_MODAL,
  Constants.modals.TOGGLE_ACCOUNT_DETAILS_MODAL,
  Constants.modals.TOGGLE_CC_MODAL,
  Constants.modals.TOGGLE_BILLING_MODAL,
]);

const routingMiddleware = routerMiddleware(createHistory);

const finalCreateStore = compose(
  applyMiddleware(thunk /*,logger*/, engineMiddleware, routingMiddleware),
  window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore);

const store = finalCreateStore(finalReducers);

const load = storage.createLoader(engine);
const createSelectLocationState = () => {
  let prevRoutingState, prevRoutingStateJS;
  return (state) => {
    const routingState = state.routing; // or state.routing
    if (typeof prevRoutingState === 'undefined' || prevRoutingState !== routingState) {
      prevRoutingState = routingState;
      prevRoutingStateJS = routingState.toJS();
    }
    return prevRoutingStateJS;
  };
};
load(store)
  .then(() => {
    /* I put the syncHistoryWithStore() call inside the load resolve promise.  
This way the store is loaded from storage FIRST, before the redux action of LOCATION_CHANGE happens. */
    const history = syncHistoryWithStore(createHistory, store, {
      selectLocationState: createSelectLocationState()
    });
    render(
        <Provider store={store}>
        <Router history={history} render={applyRouterMiddleware(useScroll())}>
        ......
    )
  })
  .catch(() => console.log('Failed to load previous state'));

This makes it so that redux router actions are called AFTER the state is loaded from storage.

michaelcontento commented 8 years ago

Sorry for the bad news but this package is no longer maintained. šŸ˜ž

My focus has left the react / node ecosystem and I don't have to time to keep things up to date. But if you want to step in and become the new maintainer of redux-storage and all of it parts, just ping me! šŸ˜ƒ