React-starter-kit: Persist Redux State Across Page Refresh

Created on 30 Aug 2017  Â·  7Comments  Â·  Source: kriasoft/react-starter-kit

This is pointed towards the redux and Apollo branches: as things are so now, a page refresh will return the store to it's initial state.

Has anyone implement something like redux-persist with this boilerplate?

How can I make the redux store state persist across a page refresh with SSR?

What I have done so far:

Add autoRehydrate function to /src/store/configureStore.js

import { createStore, applyMiddleware, compose } from 'redux';
import { autoRehydrate } from 'redux-persist';

{...}

if (__DEV__) {
    middleware.push(createLogger());

    // https://github.com/zalmoxisus/redux-devtools-extension#redux-devtools-extension
    let devToolsExtension = f => f;
    if (process.env.BROWSER && window.devToolsExtension) {
      devToolsExtension = window.devToolsExtension();
    }

    enhancer = compose(
      applyMiddleware(...middleware),
      autoRehydrate(),
      devToolsExtension,
    );
  } else {
    enhancer = compose(
      applyMiddleware(...middleware),
      autoRehydrate(),
    );
  }
{ ... }

Create action/reducer for persist/REHYDRATE

/src/actions/persisteRehydrate

import { PERSIST_REHYDRATE } from '../constants';

export function updateUserInfo({ name, value }) {
  return {
    type: PERSIST_REHYDRATE,
    payload: {
      name,
      value,
    },
  };
}

/src/reducers/persistRehydrate

import { PERSIST_REHYDRATE } from '../constants';

export default function persistRehydrate(state = {}, action) {
  switch (action.type) {
    case PERSIST_REHYDRATE:
      return {
        ...state,
        [action.payload.name]: action.payload.value,
      };
    default:
      return state;
  }
}

Add persistStore function to /src/client.js

import { persistStore } from 'redux-persist';

{ ... }

const store = configureStore(undefined, {
  apolloClient,
  fetch,
  history
});

persistStore(store, {storage: localForage}, () => {
  console.log("resolved!");
});

const context = {
  // Enables critical path CSS rendering
  // https://github.com/kriasoft/isomorphic-style-loader
  insertCss: (...styles) => {
    // eslint-disable-next-line no-underscore-dangle
    const removeCss = styles.map(x => x._insertCss());
    return () => { removeCss.forEach(f => f()); };
  },
  // For react-apollo
  client: apolloClient,
  // Initialize a new Redux store
  // http://redux.js.org/docs/basics/UsageWithReact.html
  store: store,
  fetch,
  storeSubscription: null,
};

{ ... }

Rehydration appears to be working with the above code, but the problem now is that it takes some time to rehydrate the store on the client side, routes start evaluating before the store is rehydrated which causes problems, so the application must wait for rehydration before continuing

All 7 comments

Hey @tim-soft , thanks for posting.

What version of redux-persist you were using? I just implemented v5 of it on my project with the foillowing code changes to configureStore.js.

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
import createHelpers from './createHelpers';
import createLogger from './logger';
import {persistStore, autoRehydrate} from 'redux-persist'

export default function configureStore(initialState, helpersConfig) {
  const helpers = createHelpers(helpersConfig);
  const middleware = [thunk.withExtraArgument(helpers)];

  let enhancer;

  if (__DEV__) {
    //Thunk and redux-persist
    middleware.push(createLogger());


    // https://github.com/zalmoxisus/redux-devtools-extension#redux-devtools-extension
    let devToolsExtension = f => f;
    if (process.env.BROWSER && window.devToolsExtension) {
      devToolsExtension = window.devToolsExtension();
    }

    enhancer = compose(applyMiddleware(...middleware), autoRehydrate() , devToolsExtension);
  } else {
    enhancer = compose(applyMiddleware(...middleware) , autoRehydrate() );
  }

  // See https://github.com/rackt/redux/releases/tag/v3.1.0
  const store = createStore(rootReducer, initialState, enhancer);

  // begin periodically persisting the store
  persistStore(store)

  // Hot reload reducers (requires Webpack or Browserify HMR to be enabled)
  if (__DEV__ && module.hot) {
    module.hot.accept('../reducers', () =>
      // eslint-disable-next-line global-require
      store.replaceReducer(require('../reducers').default),
    );
  }

  return store;
}

Working well so far, no need to any additional reducer/actions.

Next problem I'm tackling is the fact that page redirection based on store parameters will kick in before the rehydrate can happen, any advice is welcome.

@lvian Redux Persist v5 does not have autoRehydrate so it might be your are using v4?

How to use redux-persist in this project? Can show demo?

This may be problematic.

In general, you should prepare state on server side by calling redux actions based on session or user state.
This state should be updated incrementally by client on server, so you then should be able to re-create state on server without data loss.

On client, you can store really small subset of all, so it can help prevent data loss when connection is lost.

Another way is to skip server side rendering, instead you can return only blank page with boot code and do the rest on client only. This makes sense if user is authenticated and page with private data is being prepared.

Still, keep in mind that if user load page on different device or browser, client only data will be lost or may cause conflict in data model.

Persisting redux has a few practical benefits. Imagine storing some UI state in redux, if you refresh the page then this state is reset to it's initial value which can be annoying for the user -- storing this data elsewhere is annoying ¯\_(ツ)_/¯

The solution has been suggested more or less in this thread so I'll go ahead and close.

What about saving some information in url? If you think about selected tab or similar.. That way sharing an url will have expected behavior

@langpavel saving things like tab keys in the URL usually make sense. React components lose their state when the re-render, I'm thinking about situations where there is _a lot_ of UI state to remember across re-renders/refreshes that I also wouldn't want to replicate when users share URLs. Managing a lot of stuff in redux is a lot more practical than URL vars/fragments

Was this page helpful?
0 / 5 - 0 ratings

Related issues

scazzy picture scazzy  Â·  3Comments

rochadt picture rochadt  Â·  3Comments

artkynet picture artkynet  Â·  4Comments

piglovesyou picture piglovesyou  Â·  3Comments

Jeff-Kook picture Jeff-Kook  Â·  3Comments