react-stack/redux-storage

Loading from the storage does not overwrite initial state completely

georgjaehnig opened this issue · 6 comments

Hi, it seems that loading from the storage does not overwrite the initial state completely, and that looks like a bug for me. ( If it's not, please tell me how I can work with it productively. :) )

An example: Let's say, this is my initial state:

state = {
  ...
  test: {
    foo: 1,
  }
  ...
}

Then, in some dispatch, I change the value of test:

nextState.test = { bar: 2 };

With redux-storage set up, this is saved (I guess).

When I now close/reopen or reload the whole app, I have this state:

state = {
  ...
  test: {
    bar: 2,
    foo: 1,
  }
  ...
}

I think, there should be no foo: 1 at this point, should it?

At least it's bugging me in my app, so what's the correct way of getting rid of it? Is there a config option for redux-storage that I can set?

(See the whole code here.)

AFAIK the load give you the state, so it's up to you to write a reducer to update the current state created by the create store.

I have also try a different workaround: Put a fake store to call the loader, then init the store and pass the persisted state as an 'initial state'.

Because it's the first time I'm using it, I'm not sure this is the way to go, but nothing in the load state API is written to update the current state.

Just remember to call load the store should exists so there is already a state.

I came across the same issue @georgjaehnig and I've also expected that the state will be overwritten. And I also don't know if it's a lack of understanding or just a bug :D But looks more likely like the second one.

@jmfrancois If the load function would just give you the state it should also not modify anything in the state (but it does) and also it should not need any store object to function properly.

+1, this is very confusing. The previous version this was forked from definitely loaded the stored state upon calling load(store). This seems to fire the REDUX_STORAGE_LOAD action but doesn't actually update the redux state... is it required to import LOAD from redux-storage in my own reducer?

Actually, my issue is that when I try to introduce redux-devtools-extension, it breaks the actual loading of the storage...

It's working for me, including redux devtools. My setup in case anyone's interested:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import axios from 'axios';
import * as storage from 'redux-storage';
import createEngine from 'redux-storage-engine-localstorage';
// reducers are exported already combined
import combinedReducers from '../reducers';

// my app is server rendered so initialState would be a rehydrated object
function configureStore(initialState) {
  // Init localstorage provider
  const engine = createEngine('my-store');
  const reducer = storage.reducer(combinedReducers);
  const lsMiddleware = storage.createMiddleware(engine);
  const load = storage.createLoader(engine);

  const enhancers = compose(
    // Middleware store enhancer.
    applyMiddleware(
      // redux thunk
      thunk.withExtraArgument({ axios }),
      // localstorage middleware
      lsMiddleware,
    ),
    // Redux Dev Tools
    process.env.NODE_ENV === 'development' &&
      typeof window !== 'undefined' &&
      typeof window.devToolsExtension !== 'undefined'
      ? // Call the brower extension function to create the enhancer.
        window.devToolsExtension()
      : // Else we return a no-op function.
        f => f,
  );


  const cachedStore = typeof window !== 'undefined'
    ? !!localStorage.getItem('my-store')
    : false;

  const store = initialState && !cachedStore
    ? createStore(reducer, initialState, enhancers)
    : createStore(reducer, enhancers);

  if (cachedStore) {
    load(store);
  }

// only needed if you're using hot module replacement
  if (process.env.NODE_ENV === 'development' && module.hot) {
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers').default; // eslint-disable-line global-require

      store.replaceReducer(nextRootReducer);
    });
  }

  return store;
}

export default configureStore;

I was having the same issue, but found it to be a copy/paste mistake. I missed/overlooked/forgot to add the reducer created by storage.reducer(rootReducer) to my call to createStore call. Loading the reducer is the key to all of this happening.

One other issue I've found that you want to be careful of, if you persist complex objects for instance Moment, you will need to handle the LOAD event in your reducer and convert it to an object. You can also probably do this using the optional parameters replacer, reviver to createEngine if it supports them; redux-storage-engine-localstorage does.