Home > Net >  "Cannot read properties of null (reading 'token') " Reacjs, Redux
"Cannot read properties of null (reading 'token') " Reacjs, Redux

Time:11-21

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. 
}
  • Related