Search code examples
reactjsredux

React Redux and local storage


I am exploring new thing now a days and started learning react and redux. I have a question.

I know if we want to manage the state then we use useState hooks but its in a one component and If we want to access the state in some other components, then we have to do prop drilling through context api or useContext. Redux solves this prob by saving states as a global and store is accessbile to all components.

My first question is If we referesh the page through brower url, then all redux got flushed out. Suppose We have a page welcome and it is shown to user after login, I have applied here some condition like (show specific button to specific user) and to achieve this, we have applied conditions using the states which are saved in redux store. If user refreshes page through url, then no variables in redux store will be found which will crash the app. What to do in such situations??

My second question is what is the actual use of local storage. I mean if we are saving redux store or redux states in localstorage to avoid app crashes, then why there is a need of redux while you actually saving redux states to local storage. (why not only use local storage then).

I am very confused about all this.


Solution

  • To answer your first question, I'll use an example. Lets say your redux store has 3 keys, appPhase, darkMode, and someOtherState. It'd look something like this:

    //store.js
    import { configureStore } from "@reduxjs/toolkit";
    
    const initialState = { appPhase: 'homePage', darkMode: false, someOtherState: "blah" };
    
    const reducer = (state = initialState, action) => {
      switch (action.type) {
        case "UPDATE_PHASE": {
          return { ...state, appPhase: action.payload };
        }
        case "TOGGLE_DARK_MODE": {
          return { ...state, darkMode: action.payload };
        }
        case "UPDATE_SOME_OTHER_STATE": {
          return { ...state, someOtherState: action.payload };
        }
        default:
          return state;
      }
    };
    
    const configureAppStore = (preloadedState) => {
      const store = configureStore({
        reducer: reducer,
        preloadedState: preloadedState,
      });
    
      return store;
    };
    
    const store = configureAppStore(initialState);
    
    export default store;
    
    
    

    Lets say your app has four appPhases, something like "homepage", "browseProducts", "shoppingCart" and "checkout". The initialState for appPhase is "homepage", and appPhase is updated as the user progresses through your app. At some point, the user is in the "checkout" phase, and they've turned on darkMode, but the page is refreshed, and those two values are returned to their initial state { appPhase: 'homePage', darkMode: false }, the user is back at the home page and they have to turn darkMode on again.

    This is where you could use localStorage to persist the two states. There are two parts to that, writing a middleware that stores those two states in localStorage, and rehydrating the redux store with the persisted states.

    You could write a store middleware that listens for the two actions "UPDATE_PHASE" and "TOGGLE_DARK_MODE", and anytime one of those actions are dispatched, the middleware takes the value and sets it in localStorage.

    The middleware could look something like this:

      const localStorageMiddleware = ({ getState }) => {
        return (next) => (action) => {
          const result = next(action);
          if (action.type === 'UPDATE_PHASE') {
            localStorage.setItem(
              'applicationState',
              `{
                  "appPhase": ${JSON.stringify(getState().appPhase)}
                }`
            );
          }
          if (action.type === 'TOGGLE_DARK_MODE') {
            localStorage.setItem(
              'applicationState',
              `{
                  "darkMode": ${JSON.stringify(getState().darkMode)}
                }`
            );
          }
    
          return result;
        };
      };
    

    Now, any time the appPhase or darkMode change, not only are we updating the store, we're also setting it in localStorage.

    There's one more step though, we aren't rehydrating our redux store with the states we've persisted in localStorage. If we were to refresh, our store would still reflect our initialState, and not the values we've persisted in localStorage.

    The rehydrate method would look something like:

      const reHydrateStore = () => {
        let localstore;
        if (localStorage.getItem('applicationState')) {
          localstore = JSON.parse(localStorage.getItem('applicationState'));
        }
        // Merge InitialState with stuff in localstore
        return {...preloadedState, localstore};
      };
    

    If localStorage has applicationState, it would be something like {appPhase: "checkout", darkMode: true}, and it would merge that into your preloadedState without effecting someOtherState (something we didn't want to persist). Our rehydrated store would reflect both our initialState and the states we persisted in localStorage:

    { appPhase: 'checkout', darkMode: true, someOtherState: "blah" }
    

    We'd add our middleware and reHydrateStore method via configureStore(), as shown below:

    //store.js
    import { configureStore } from "@reduxjs/toolkit";
    
    const initialState = { appPhase: 'homePage', darkMode: false, someOtherState: "blah" };
    
    const reducer = (state = initialState, action) => {
      switch (action.type) {
        case "UPDATE_PHASE": {
          return { ...state, appPhase: action.payload };
        }
        case "TOGGLE_DARK_MODE": {
          return { ...state, darkMode: action.payload };
        }
        case "UPDATE_SOME_OTHER_STATE": {
          return { ...state, someOtherState: action.payload };
        }
        default:
          return state;
      }
    };
    
      const localStorageMiddleware = ({ getState }) => {
        return (next) => (action) => {
          const result = next(action);
          if (action.type === 'UPDATE_PHASE') {
            localStorage.setItem(
              'applicationState',
              `{
                  "appPhase": ${JSON.stringify(getState().appPhase)}
                }`
            );
          }
          if (action.type === 'TOGGLE_DARK_MODE') {
            localStorage.setItem(
              'applicationState',
              `{
                  "darkMode": ${JSON.stringify(getState().darkMode)}
                }`
            );
          }
    
          return result;
        };
      };
    
      const reHydrateStore = () => {
        let localstore;
        if (localStorage.getItem('applicationState')) {
          localstore = JSON.parse(localStorage.getItem('applicationState'));
        }
        // Merge InitialState with stuff in localstore
        return { ...preloadedState, localstore };
      };
    
      const store = configureStore({
        reducer: reducer,
        middleware: [localStorageMiddleware],
        preloadedState: reHydrateStore(),
      });
    
      return store;
    };
    
    const store = configureAppStore(initialState);
    
    export default store;
    
    

    There's also redux-persist, which handles persisting application state and rehydrating the store in both web and mobile apps. It provides a component rendering delay until the state is persisted, whitelists and blacklists of which reducers to persist, and more.

    As far as your second question, "why use redux at all instead of localstorage", I don't know that I can do the answer justice. They're used very often in combination, but I'm not sure about using localStorage solely as a state management tool.

    It's more performant to read/write to/from your redux store than reading and writing to localStorage and parsing json every time the state changes. You'd need to be creative in distributing localStorage changes to your components, whereas tools like redux are meant for that. Also, anyone on the client side can view and modify data in localStorage by using the developer console, so storing anything sensitive in localStorage can be problematic. Its worth a google if you're interested in the differences.