Search code examples
javascriptangularngrx

NgRx add an object to the list of objects


I have a state with the following structure. It contains a list of Workouts and each workout has a list of exercises related to this workout. I want to be able to do 2 things:

  1. add new exercises to the specific workout from the list of workouts
  2. delete a specific exercise from the specific workout

E.g. In my UI I can add new exercises to Workout with the name Day 2. So my action payload gets 2 params: workout index (so I can find it later in the state) and exercise that should be added to/deleted from the list of exercises of the specific workout.

State

state = {
 workouts: [
  {
    name: "Day 1",
    completed: false,
    exercises: [{
      name: "push-up",
      completed: false
    },
    {
      name: "running",
      completed: false
    }]
  },
  {
    name: "Day 2",
    completed: false,
    exercises: [{
      name: "push-up",
      completed: false
    }]
  },
  {
    name: "Day 3",
    completed: false,
    exercises: [{
      name: "running",
      completed: false
    }]
   }]
}

Actions

export class AddExercise implements Action {
  readonly type = ADD_EXERCISE
  constructor(public payload: {index: number, exercise: Exercise}) {}
}

export class DeleteExercise implements Action {
  readonly type = DELETE_EXERCISE
  constructor(public payload: {index: number, exercise: Exercise}) {}
}

And I am stuck on the reducer. Can you advise how it should be done properly? This is how it looks right now (not finalized yet):

Reducer

export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
  switch(action.type) {
    case WorkoutActions.ADD_EXERCISE:
       const workout = state.workouts[action.payload.index];
       const updatedExercises = [
         ...workout.exercises,
         action.payload.exercise
       ]
       return {
         ...state,
          workouts: [...state.workouts, ]
       }
    return {};
    default:
      return state;
  }
}

Thank you!


Solution

  • Please, try something like the following (I included comments within the code, I hope it makes it clear):

    export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
      switch(action.type) {
        case WorkoutActions.ADD_EXERCISE:
           // You can take advantage of the fact that array map receives 
           // the index as the second argument
           // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
           const workouts = state.workouts.map((workout, index) => {
             if (index != action.payload.index) {
               return workout;
             }
    
             // If it is the affected workout, add the new exercise
             const exercises = [
               ...workout.exercises,
               action.payload.exercise
             ]
    
             return { ...workout, exercises }
           })
           
           // return the updated state
           return {
             ...state,
             workouts
           }
    
        case WorkoutActions.DELETE_EXERCISE:
           // very similar to the previous use case
           const workouts = state.workouts.map((workout, index) => {
             if (index != action.payload.index) {
               return workout;
             }
    
             // the new exercises array will be composed by every previous
             // exercise except the provided one. I compared by name, 
             // I don't know if it is accurate. Please, modify it as you need to
             const exercises = workout.exercises.filter((exercise) => exercise.name !== action.payload.exercise.name);
    
             return { ...workout, exercises }
           })
           
           // return the new state 
           return {
             ...state,
             workouts
           }
    
        default:
          return state;
      }
    }