Redux-persist: State lost after page reload. How to resolve this?

Created on 1 Jun 2017  路  5Comments  路  Source: rt2zz/redux-persist

OS: Windows 10 Pro
redux-persist: "^4.8.0"

So after a reload of any of my Router paths is made, state becomes === undefined. But I want the state, as of that moment in time, to remain intact after a page reload. How do I ensure that this happens?

app.js

render(
  <ApolloProvider store={store} client={client}>
    { /* Tell the Router to use our enhanced history */ }
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={PhotoGrid} />
        <Route path="/view/:postId" component={Single}></Route>
        <Route path="/login" component={LoginUser}></Route>
      </Route>
    </Router>
  </ApolloProvider>,
  document.getElementById('root')
);

store.js

import { persistStore, autoRehydrate} from 'redux-persist';
import rootReducer from './reducers/index';
import localForage from 'localforage';

const middlewares = [thunk, client.middleware()];

const enhancers = compose(
    applyMiddleware(...middlewares),
    (typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined' || process.env.NODE_ENV !== 'production') ? window.__REDUX_DEVTOOLS_EXTENSION__() : (f) => f,
    autoRehydrate(),
);

const defaultState = {
  auth: {}
};

const store = createStore(
  rootReducer,
  undefined, // {}, // initial state
  enhancers
);

// begin periodically persisting the store
persistStore(store, {storage: localForage});

export const history = syncHistoryWithStore(
  browserHistory, 
  store
);

if(module.hot) {
  module.hot.accept('./reducers/', () => {
    const nextRootReducer = require('./reducers/index').default;
    store.replaceReducer(nextRootReducer);
  });
}

export default store;

index.js (Root reducer)

const rootReducer = combineReducers({
  auth: tokenDetails,
  logout: logOut,
  routing: routerReducer,
  apollo: client.reducer(),
});

export default rootReducer;

App.js

const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
  options: {
    cachePolicy: 'offline-critical', 
    fetchPolicy: 'cache-first',
  },
});

function mapStateToProps(state) {
   return {
    auth: state.auth
  };
}

export const mapDispatchToProps = (dispatch) => {
  return bindActionCreators(actionCreators, dispatch);
}

export default compose(
  allPostsCommentsQuery,
  connect(mapStateToProps, mapDispatchToProps)
)(Main);

tokenDetials.js (Reducer)

var tokenDetails = function(state, action) {

  if (state === undefined) {
    console.log('state = undefined, in tokenDetails.js, and has been reset to {}');
    state = {};
  }

  switch (action.type) {
    case 'Graphcool_Token':
      const newState = { ...state, token: action.payload };
      // console.log('newState = ', newState);
      return newState;
    default:
      return state;
  }
}

export default tokenDetails;

And do I need to do something like the following in each of my components to ensure that state is persisted, or is persistStore(store, {storage: localForage}); specified in my store.js enough?:

  componentWillMount () {
    persistStore(this.props.store, {storage: Localforage}, () => {
      this.setState({ rehydrated: true })
    })
  }

Most helpful comment

For me below works fine.

  • Configure store
  • import store before in ReactDom.Render
  • persist the store, after rehydration I am rendering dom.
...................
import configureStore from './store';
import {persistStore, autoRehydrate} from 'redux-persist'

const store =  configureStore();
persistStore(store, {}, () => {
  ReactDOM.render((
    <Provider store={store}>
      <Router>
        <App/>
      </Router>
    </Provider>
  ), document.getElementById('root'));
  registerServiceWorker();  
})

All 5 comments

Thanks to he wonderment that is logger the following two images show exactly what I'm experiencing. Note: in addition to store.js, specified in my initial question, I have also added the following code to each reducer:

import {REHYDRATE} from 'redux-persist/constants';
    case REHYDRATE:
      var incoming = action.payload.logOut
      if (incoming) return {...state, ...incoming, specialKey: processSpecial(incoming.specialKey)}
      return state

The first image reflects state directly after login and the users authToken has been captured:

initial_login

The next image reflects state directly after a reload has occurred, clearly showing that authToken no longer exists:

after_reload

How do I retain state as it existed prior to the reload?

Hi TheoMer, I had a similar issue when refreshing the browser. It was because the component render before the rehydratation was complete. Maybe you need to delay the render:

https://github.com/rt2zz/redux-persist/blob/master/docs/recipes.md

This helped me with that, I put the persistStore in my main component:

componentWillMount() {
persistStore(store, {}, () => {
this.setState({rehydrated: true})
})
}

This will ensure that the component re render when the store is rehydrated.

And do I need to do something like the following in each of my components to ensure that state is persisted, or is persistStore(store, {storage: localForage}); specified in my store.js enough?:

  componentWillMount () {
    persistStore(this.props.store, {storage: Localforage}, () => {
      this.setState({ rehydrated: true })
    })
  }

I only put that in my main component, the router:

componentWillMount() {
        persistStore(store, {}, () => {
            this.setState({rehydrated: true})
        })
    }

render() {
        if (!this.state.rehydrated) {
            return <div>Loading...</div>
        }
        return ( 
            <Provider store={store}>
                <HashRouter history={history}>
                    <Switch>
                        <Route ... />
                        <Route ... />
                         <Route ... />
                    </Switch>
                </HashRouter>
            </Provider>
        );
    }

@glowind Solution found.

Rehydration was not working due to the order of autoRehydrate() in my enhancers in store.js. It MUST appear before devTools!

const enhancers = compose(
    applyMiddleware(...middlewares),
    autoRehydrate(),
    (typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined' || process.env.NODE_ENV !== 'production') ? window.__REDUX_DEVTOOLS_EXTENSION__() : (f) => f,
);

I was able to then remove all of the rehydrate code from my main component (router), so it reads as follows:

class MainApp extends React.Component {
  constructor(props) {
    super(props);
  }

  render () {
    return (
      <ApolloProvider store={store} client={client}>
        <Router history={history}>
          <Route path="/" component={App}>
            <IndexRoute component={PhotoGrid} />
            <Route path="/view/:postId" component={Single}></Route>
            <Route path="/login" component={LoginUser}></Route>
          </Route>
        </Router>
      </ApolloProvider>
    )
  }
}

export default MainApp;

And all works as expected now.

For me below works fine.

  • Configure store
  • import store before in ReactDom.Render
  • persist the store, after rehydration I am rendering dom.
...................
import configureStore from './store';
import {persistStore, autoRehydrate} from 'redux-persist'

const store =  configureStore();
persistStore(store, {}, () => {
  ReactDOM.render((
    <Provider store={store}>
      <Router>
        <App/>
      </Router>
    </Provider>
  ), document.getElementById('root'));
  registerServiceWorker();  
})
Was this page helpful?
0 / 5 - 0 ratings

Related issues

elado picture elado  路  4Comments

Clumsy-Coder picture Clumsy-Coder  路  3Comments

scic picture scic  路  3Comments

bockc picture bockc  路  3Comments

benmvp picture benmvp  路  3Comments