首页 > 解决方案 > Redux Persist - cannot read property of null (reading 'user')

问题描述

Hello everyone I am trying to build an E-commerce project using MERN but I have some difficulties with redux-persist.

In the App.jsx the useSelector does not read the user from state

Property 'user' does not exist on type 'DefaultRootState'

Also, in requestMethod TOKEN does not read the user, it says "cannot read property of null ("user"). Before using redux-persist, the register and login worked.

I posted the code I think there are some bugs but if you think the problems come from somewhere else I can add those files too. Thank you!

Package.json

 "dependencies": {
    "@material-ui/core": "^4.12.3",
    "@material-ui/icons": "^4.11.2",
    "@reduxjs/toolkit": "^1.6.2",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^11.2.7",
    "@testing-library/user-event": "^12.8.3",
    "axios": "^0.21.4",
    "json-server": "^0.16.3",
    "mongodb": "^4.1.3",
    "mongoose": "^6.0.9",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-modal": "^3.14.3",
    "react-redux": "^7.2.5",
    "react-reveal": "^1.2.2",
    "react-router-dom": "^5.3.0",
    "react-scripts": "4.0.3",
    "react-stripe-checkout": "^2.6.3",
    "redux": "^4.1.1",
    "redux-devtools-extension": "^2.13.9",
    "redux-persist": "^6.0.0",
    "redux-thunk": "^2.3.0",
    "reduxjs-toolkit-persist": "^7.0.7",
    "shortid": "^2.2.16",
    "styled-components": "^5.3.1",
    "web-vitals": "^1.1.2"
  },

Index.jsx

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

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>,
  document.getElementById("root")
);

App.jsx

const App = () => {
  const user = useSelector((state) => state.user.currentUser);
  return (
    <Router>
      <Switch>
        <Route exact path="/">
          <Home/>
        </Route>
        <Route path="/products/:category">
          <ProductList/>
        </Route>
        <Route path="/product/:id">
          <Product />
        </Route>
        <Route path="/cart">
          <Cart/>
        </Route>
        <Route path="/success">
          <Success/>
        </Route>
        <Route path="/login">
          {user ? <Redirect to ="/"/> : <Login/>}
          <Login/>
        </Route>
        <Route path="/register">
        {user ? <Redirect to ="/"/> : <Register/>}

state.user

Object
cart:
products: []
quantity: 0
total: 0
[[Prototype]]: Object
user:
currentUser: {_id: '616001631a1375942f6d7dd9', username: 'admin', email: 'admin@gmail.com', isAdmin: true, createdAt: '2021-10-08T08:29:23.827Z', …}
error: false
isFetching: false
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
_persist:
rehydrated: true
version: 1
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

Store.js

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import cartReducer from "./cartRedux";
import userReducer from "./userRedux";
import {
    persistStore,
    persistReducer,
    FLUSH,
    REHYDRATE,
    PAUSE,
    PERSIST,
    PURGE,
    REGISTER,
} from "redux-persist";
import storage from "redux-persist/lib/storage";


const persistConfig = {
    key: "root",
    version: 1,
    storage,
};

const rootReducer = combineReducers({ user: userReducer, cart: cartReducer })

const persistedReducer = persistReducer(persistConfig, rootReducer);


export const store = () => configureStore({
    reducer:
        persistedReducer,
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: {
                ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
            },
        })
})


export let persistor = persistStore(configureStore);

requestMethods.js

import axios from "axios";

const BASE_URL = "http://localhost:5000/api/";
const TOKEN =
    JSON.parse(JSON.parse(localStorage.getItem('persist:root')).user).currentUser
        .accessToken || "";

export const publicRequest = axios.create({
    baseURL: BASE_URL,
});

export const userRequest = axios.create({
    baseURL: BASE_URL,
    header: { token: `Bearer ${TOKEN}` },
});

User Redux

import { createSlice } from "@reduxjs/toolkit";

const userSlice = createSlice({
    name: "user",
    initialState: {
        currentUser: null,
        isFetching: false,
        error: false,
    },
    reducers: {
        loginStart: (state) => {
            state.isFetching = true;
        },
        loginSuccess: (state, action) => {
            state.isFetching = false;
            state.currentUser = action.payload;
        },
        loginFailure: (state) => {
            state.isFetching = false;
            state.error = true;
        },
    },
});

export const { loginStart, loginSuccess, loginFailure } = userSlice.actions;
export default userSlice.reducer;

标签: reactjsreduxredux-persist

解决方案


There are some little issues with your implementation. let's review them:

First, you are getting the user object from the state with a selector, your user variable (in App component) is now an object.

const yourStateObject = {
  user: {
    currentUser: {_id: '616001631a1375942f6d7dd9', username: 'admin', email: 'admin@gmail.com', isAdmin: true, createdAt: '2021-10-08T08:29:23.827Z', ...}
  }
}

Now, take a look at result of your selector:

const user = useSelector(state => state.user.currentUser)

console.log(user) // {_id: '616001631a1375942f6d7dd9', username: 'admin', email: 'admin@gmail.com', isAdmin: true, createdAt: '2021-10-08T08:29:23.827Z', ...}

The Problem

So far, so good, but in the Router you are trying to check the user object with the ternary operator.

consider this example:

const myObject = {}

console.log(myObject ? "Yes this is an object" : "Nothing exist") 

console.log(myAnotherObject ? "Yes this is an object" : "Nothing exist") 

since the myObject is defined, the left side of the ternary result will be returned, but in the second console.log, the right side will be returned because the myAnotherObject is not defined and evaluated as undefined.

The Solution

instead of evaluating the user object to return the correct route, check for the user id or username.

const userId = useSelector(state => state.user.currentUser._id)

return (
  // rest of the codes ...
  {userId ? <Redirect to ="/"/> : <Login/>}
  // rest of the codes ...
)

Also, you need to use {} as initialState in userSlice instead of null

const userSlice = createSlice({
    name: "user",
    initialState: {
        currentUser: {},   // ------> here
        isFetching: false,
        error: false,
    },
   // rest of the codes ...

Token

you are getting the token from the persistor storage, which is not correct. instead of retrieving the token from the persistor storage, save it on the localStorage directly and then get it when needed.

// set token
localStorage.setItem('ACCESS_TOKEN', token);

// get token
const myToken = localStorage.getItem('ACCESS_TOKEN)

推荐阅读