javascriptreactjsreact-reduxredux-toolkitredux-persist

Redux-Persist doesn't store state in the local storage


I'm trying to persist specific reducer state to localStorage so that it wouldn't be disappear even though the browser is refreshed.

I've written some code, but in the storage all I can see is just like this:

"persist:root", "{"_persist":{\"version\":-1,\"rehydrated\":true}"}"

Tthere's no user data. What am I missing?

FYI, the reason why I need to store user data as global variable is that I have to re-render depending on this value. The problem is that when the browser is refreshed, it keeps navigating to loginPage. I've checked redux dev tools, the user data is set properly in the Login component.

store.js

import { configureStore, createSlice } from '@reduxjs/toolkit'
import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';
import { persistStore } from 'redux-persist';

const persistConfig = {
  key: 'root',
  storage: storage,
  whitelist: ['user']
};

let user = createSlice({
  name: 'user',
  initialState: {
    userId: 'anonymous',
    isLoggedIn: false
  },
  reducers: {
    changeUser(state, action) {
      state.userId = action.payload.userId;
      state.isLoggedIn = action.payload.isLoggedIn;
    }
  }
})

const persistedReducer = persistReducer(persistConfig, user.reducer);

export let { changeUser } = user.actions;

let store = configureStore({
  reducer: {
    user: persistedReducer
  }
  // persistedReducer
})

export const persistor = persistStore(store);
export default store;

App.js

function App(){
  const isLoggedIn = useSelector((state) => state.user.isLoggedIn);

  return (
    <BrowserRouter>
      <RedirectToLogin />
      <Center>
        {isLoggedIn && <Sidebar userRole={''} />}
        <Routes>
          <Route path="/loginPage" element={<Login />} />
          {isLoggedIn ? (
            <>
              <Route element={<ProtectedRoute isLoggedIn={isLoggedIn} />} />
              <Route path="/" element={<Editor/>}/>
              <Route path={"/loginPage"} element={<Login/>}/>
              <Route path="*" element={<NotFoundPage/>}/>
            </>
          ) : (
            <>
              <Route path="*" element={<Navigate to="/loginPage" replace />} />
            </>
          )}
        </Routes>
      </Center>
    </BrowserRouter>
  )
}

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store, {persistor} from "./store";
import {Provider} from "react-redux";
import { PersistGate } from "redux-persist/integration/react";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>
);

reportWebVitals();

Solution

  • Issue

    The issue it seems is with the persistence configuration, specifically what it whitelists, and what reducer function it is persiting:

    const persistConfig = {
      key: 'root',
      storage: storage,
      whitelist: ['user']
    };
    

    This persists the user key of the reducer you are persisting, e.g. user.reducer, but the user slice's reducer doesn't have any user key to persist, it's just the direct user.reducer function.

    const user = createSlice({
      name: 'user',
      initialState: {
        userId: 'anonymous',
        isLoggedIn: false
      },
      reducers: {
        changeUser(state, action) {
          state.userId = action.payload.userId;
          state.isLoggedIn = action.payload.isLoggedIn;
        }
      }
    })
    
    const persistedReducer = persistReducer(persistConfig, user.reducer);
    

    Solution

    Either persist the entire user.reducer function, e.g. no whitelisting in the user.reducer:

    import { configureStore, createSlice } from '@reduxjs/toolkit';
    import storage from 'redux-persist/lib/storage';
    import { persistReducer } from 'redux-persist';
    import { persistStore } from 'redux-persist';
    
    const user = createSlice({
      name: 'user',
      initialState: {
        userId: 'anonymous',
        isLoggedIn: false
      },
      reducers: {
        changeUser(state, action) {
          state.userId = action.payload.userId;
          state.isLoggedIn = action.payload.isLoggedIn;
        }
      }
    });
    
    export const { changeUser } = user.actions;
    
    const persistConfig = {
      key: 'root',
      storage: storage,
    };
    
    const persistedUserReducer = persistReducer(persistConfig, user.reducer);
    
    const store = configureStore({
      reducer: {
        user: persistedUserReducer
      }
    })
    
    export const persistor = persistStore(store);
    export default store;
    

    Or create a root reducer function, e.g. "combineReducers", and whitelist the user state.

    import {
      configureStore,
      createSlice,
      combineReducers
    } from '@reduxjs/toolkit';
    import storage from 'redux-persist/lib/storage';
    import { persistReducer } from 'redux-persist';
    import { persistStore } from 'redux-persist';
    
    const user = createSlice({
      name: 'user',
      initialState: {
        userId: 'anonymous',
        isLoggedIn: false
      },
      reducers: {
        changeUser(state, action) {
          state.userId = action.payload.userId;
          state.isLoggedIn = action.payload.isLoggedIn;
        }
      }
    });
    
    const persistConfig = {
      key: 'root',
      storage: storage,
      whitelist: ['user']
    };
    
    export const { changeUser } = user.actions;
    
    const rootReducer = combineReducers({
      user: user.reducer,
    });
    
    const persistedRootReducer = persistReducer(persistConfig, rootReducer);
    
    const store = configureStore({
      reducer: persistedRootReducer,
    })
    
    export const persistor = persistStore(store);
    export default store;