React Redux How to ensure Item is deleted without needing to refresh page using dispatch and slice

Viewed 26

I am working on the following tutorial: https://www.youtube.com/watch?v=UXjMo25Nnvc&list=PLillGF-RfqbbQeVSccR9PGKHzPJSWqcsm&index=4

Full GitHub project here from Brad Traversy: https://github.com/bradtraversy/mern-tutorial

For the Delete Goals section (starting at 37:06 to 44:56 timestamp)

Dashboard file: https://github.com/htqanh305/vocab-app/blob/main/frontend/src/pages/Dashboard.jsx

import {useEffect} from 'react'
import {useNavigate} from 'react-router-dom' // for redirecting
import {useSelector, useDispatch} from 'react-redux' // grab user to check state
import GoalForm from '../components/GoalForm'
import GoalItem from '../components/GoalItem'
import Spinner from '../components/Spinner'
import {getGoals, reset} from '../features/goals/goalSlice'


function Dashboard() {
  const navigate = useNavigate()
  const dispatch = useDispatch()

  const {user} = useSelector((state) => state.auth) 
  const {goals, isLoading, isError, message} = useSelector((state) => state.goals)



  useEffect(() => {
    if(isError) {
      console.log(message)
    }

    if(!user) {
      navigate('/login')
    }

    dispatch(getGoals())
    console.log("I reached this point")

    return () => {
      dispatch(reset())
    }
   
  }, [user, navigate, isError, message, dispatch])

  if(isLoading) {
    return <Spinner />
  }


 if(!user) {
    navigate('/login')
  } else {
    return (
      <>
        <section className="heading">
          <h1>Welcome {user.name} </h1>
          <p>Goals Dashboard</p>
        </section>

        <GoalForm/>

        <section className="content">
          {goals.length > 0 ? (
            <div className="goals">
              {goals.map((goal) => (
                <GoalItem key={goal._id} goal={goal} />
              ))}

            </div>
          ) : 
          (<h3> You have not set any goals </h3>)} 

        </section>
      </>
    )

}
}

export default Dashboard

goalSlice file: https://github.com/bradtraversy/mern-tutorial/blob/main/frontend/src/features/goals/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
//thunkAPI object has a getState method that helps get anything we want/ any part of the state
// ie. get auth state to get token to access user so we can set/get goals
export const createGoal = createAsyncThunk('goals/create', async(goalData, thunkAPI) => {
    try {
        const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
        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 // get token from outside of goal state (auth state)
        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)
    }
})

// Delete goal
export const deleteGoal = createAsyncThunk('goals/delete', async(id, thunkAPI) => {
    try {
        const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
        return await goalService.deleteGoal(id, 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
            })
            .addCase(deleteGoal.pending, (state) => {
                state.isLoading = true
            })
            .addCase(deleteGoal.fulfilled, (state, action) => {
                state.isLoading = false
                state.isSuccess = true
                // filter out the UI when delete a goal, only show goals that are not deleted
                console.log("confirm")
                state.goals = state.goals.filter(
                    goal => goal._id !== action.payload.id)
                    console.log(action.payload.id)
                    console.log(state.goals)
    
            })
            .addCase(deleteGoal.rejected, (state, action) => {
               state.isLoading = false
               state.isError = true
               state.message = action.payload
            })
    }
})

export const {reset} = goalSlice.actions
export default goalSlice.reducer

GoalItem file:

import {useDispatch} from 'react-redux'
import { deleteGoal } from '../features/goals/goalSlice'

function GoalItem({goal}) {
    const dispatch = useDispatch()

    return (
    <div className="goal">
        <div>
            {new Date(goal.createdAt).toLocaleDateString('en-US')}
        </div>
        <h2>{goal.text}</h2>

        <button onClick={() => dispatch(deleteGoal(goal._id))} className="close">X</button>
    </div>
  )
}

export default GoalItem

I'm following along in the tutorial, and no matter how much I try, I can't seem to get the delete goal functionality working.

When I create a new goal (item) and try to delete it, the item still remains on the page until I refresh or click on it twice. I'm not sure if it's something wrong with the goalSlice function or my Dashboard file, but for some reason it's not working.

I followed the tutorial to a tee, but it still looks like it isn't doing what it's supposed to.

Does anyone happen to know what is causing this Redux issue, or if I'm going crazy? Any suggestions or advice would be helpful. Thanks!

0 Answers
Related