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;
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', ...}
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
.
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 ...
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)