I am watching Brad Traversy MERN Tutorial. In which he has created a Goal Setter app. I have almost completed it but getting an error in reset() function of goalSlice. Dont know what the actual problem is. When the reset() is dispatch from the Dashboard.jsx, and after adding goals and viewing them in redux devtool in chrome. When I press logout button, the chrome gets hang and the console has this message Cannot read properties of null (reading 'token')
I believe this is the console.log(message)
which is in Dashboard.jsx but i have no idea about resolving it. I tried commenting the dispatch(reset())
which removes this error, but I also have to reset the goals when user is logged out. Anyone have any idea about this??
I have uploaded my code on github aswell, so if anyone needs more info about code can visit this link : https://github.com/anishdalvi/MERN-Goal-Setter-.git
Youtube Link : https://youtu.be/UXjMo25Nnvc
Dashboard.jsx
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import GoalForm from '../components/GoalForm'
import Spinner from '../components/Spinner'
import { getGoals, reset } from '../features/goals/goalSlice'
function Dashboard() {
const dispatch = useDispatch()
const navigate = useNavigate()
const { user } = useSelector((state) => state.auth)
const { goals, isLoading, isError, message } = useSelector((state) => state.goals)
useEffect(() => {
if(isError){
console.log("iserror " message)
}
if(!user){
navigate('/login')
}
dispatch(getGoals())
/* return () => {
dispatch(reset())
} */
} , [user, navigate, isError, message, dispatch] )
if (isLoading){
return <Spinner />
}
return (
<>
<section className="heading">
<h1>Welcome {user && user.name}</h1>
<p>Goals Dashboard</p>
</section>
<GoalForm />
</>
)
}
export default Dashboard
goalService.js
import axios from 'axios'
const API_URL = '/api/goals/'
// create new goal
const createGoal = async (goalData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`
}
}
const response = await axios.post(API_URL, goalData, config)
return response.data
}
// get goals
const getGoals = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`
}
}
const response = await axios.get(API_URL, config)
return response.data
}
const goalService = {
createGoal,
getGoals
}
export default goalService
goalSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import goalService from './goalService'
const initialState = {
goals: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ""
}
// create new Goal
export const createGoal = createAsyncThunk('goals/create', async (goalData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token
return await goalService.createGoal(goalData, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
// get user goals
export const getGoals = createAsyncThunk('goals/getAll', async(_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token
return await goalService.getGoals(token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
export const goalSlice = createSlice({
name: 'goal',
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers:(builder) => {
builder
.addCase(createGoal.pending, (state) => {
state.isLoading = true
})
.addCase(createGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals.push(action.payload)
})
.addCase(createGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(getGoals.pending, (state) => {
state.isLoading = true
})
.addCase(getGoals.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals = action.payload
})
.addCase(getGoals.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const { reset } = goalSlice.actions
export default goalSlice.reducer
authService.js
import axios from 'axios'
const API_URL = '/api/users/'
// Register User
const register = async (userData) => {
const response = await axios.post(API_URL, userData)
if(response.data){
localStorage.setItem('user', JSON.stringify(response.data))
}
return response.data
}
// login User
const login = async (userData) => {
const response = await axios.post(API_URL 'login', userData)
if(response.data){
localStorage.setItem('user', JSON.stringify(response.data))
}
return response.data
}
// Logout User
const logout = () => {
localStorage.removeItem('user')
}
const authService = {
register, logout, login
}
export default authService
authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import authService from './authService'
// Get user from localStorage
const user = JSON.parse(localStorage.getItem('user'))
const initialState = {
user: user ? user : null,
isError: false,
isSuccess: false,
isLoading: false,
message: ""
}
// Register user
export const register = createAsyncThunk('auth/register', async (user, thunkAPI) => {
try {
return await authService.register(user)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
// Login user
export const login = createAsyncThunk('auth/login', async (user, thunkAPI) => {
try {
return await authService.login(user)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
// logout
export const logout = createAsyncThunk('auth/logout' , async () => {
await authService.logout()
})
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
reset: (state) => {
state.isLoading = false
state.isSuccess = false
state.isError = false
state.message = ''
}
},
extraReducers: (builder) => {
builder
.addCase(register.pending, (state) => {
state.isLoading = true
} )
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.user = action.payload
})
.addCase(register.rejected, (state, action) =>{
state.isLoading = false
state.isError = true
state.message = action.payload
state.user = null
})
.addCase(login.pending, (state) => {
state.isLoading = true
} )
.addCase(login.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.user = action.payload
})
.addCase(login.rejected, (state, action) =>{
state.isLoading = false
state.isError = true
state.message = action.payload
state.user = null
})
.addCase(logout.fulfilled, (state) =>{
state.user = null
})
}
})
export const { reset } = authSlice.actions
export default authSlice.reducer
CodePudding user response:
Look at this property access:
thunkAPI.getState().auth.user.token
You are trying to access the auth user's token.
But your initial state looks like this:
const initialState = {
user: user ? user : null,
isError: false,
isSuccess: false,
isLoading: false,
message: ""
}
In the case that the user doesn't exist, how can you access their token?
When you are calling reset()
, your user is probably being set to null
, hence the error only appears at that point.
You should check that the user is not null before accessing the token, and handle the case where it is null however you want.
if (thunkAPI.getState().auth.user) {
// only in the case the above condition is true,
// then you can access the token.
}