Ahmed Magdy
Ahmed Magdy

Reputation: 341

Delete an item from an Array that is inside an object (redux/typescript/react)

I am using a local json file to store some data as the follows:

[
    {
        "name": "Sports",
        "todos": [
            {
                "id": 1,
                "title": "Play Football"
            },
            {
                "id": 2,
                "title": "Play Basketball"
            }
        ]
    },
    {
        "name": "Work",
        "todos": [
            {
                "id": 3,
                "title": "Study TS"
            },
            {
                "id": 4,
                "title": "Work Hard"
            }
        ]
    }
    ]

I made a reducer that takes the two categories above and actually display all these data on the component page.

I am now trying to create another reducer that deletes a certain item from within the todos array that is within each category but I am failing. Here are the actions and the reducers I created so far

// actions

export const getCategories = () => {
    return async (dispatch: Dispatch) => {
        const response = await axios.get<Category[]>(url);

        dispatch({
            type: 'GET_ALL_CATEGORIES',
            payload: response.data
        });
    };
};

export const deleteTodo = (id: number) => {
    return {
        type: 'DELETE_A_TODO',
        payload: { id }
    };
};

// reducers
const categoriesReducer = (state: Category[] = [], action: Action) => {
    switch (action.type) {
        case 'GET_ALL_CATEGORIES':
            return action.payload;
        case 'DELETE_A_TODO':
            state.forEach(category => {
                return category.todos.filter(
                    todo => todo.id !== action.payload.id
                );
            });
        default:
            return state;
    }
};

export const reducers = combineReducers<ReduxStoreState>({
    categories: categoriesReducer
});

Then inside the component I wired up the deleteTodo action successfully but nothing gets deleted, there is no difference in the state.

What am I doing wrong?

Upvotes: 0

Views: 800

Answers (3)

Asaf Aviv
Asaf Aviv

Reputation: 11770

I would suggest to return the category name or better the category index with the id in the payload of deleteTodo action.

in the current state of your code this is one of the solutions

const categoriesReducer = (state: Category[] = [], action: Action) => {
  switch (action.type) {
    case 'GET_ALL_CATEGORIES':
      return action.payload
    case 'DELETE_A_TODO': {
      const { id } = action.payload
      const categoryIndex = state
        .findIndex(category => category.todos.some(todo => todo.id === id))

      return state.map((category, i) => i === categoryIndex
        ? {
            ...category,
            todos: category.todos.filter(todo => todo.id !== id),
          }
        : category
      )
    }
    default:
      return state
  }
}

Now if you return the category index together with the id, we don't need to iterate through every todos array to find the id

export const deleteTodo = (id: number, categoryIndex: number) => {
  return {
    type: 'DELETE_A_TODO',
    payload: { id, categoryIndex }
  }
}

const categoriesReducer = (state: Category[] = [], action: Action) => {
  switch (action.type) {
    case 'GET_ALL_CATEGORIES':
      return action.payload
    case 'DELETE_A_TODO': {
      const { id, categoryIndex } = action.payload

      return state.map((category, i) => i === categoryIndex
        ? {
            ...category,
            todos: category.todos.filter(todo => todo.id !== id),
          }
        : category
      )
    }
    default:
      return state
  }
}

Upvotes: 1

Gabor Szekely
Gabor Szekely

Reputation: 1238

Calling the forEach method does not actually return a new array in your DELETE case, and you are also not returning a new state object, hence the issue.

You need to map over your state and return new category objects with updated todo arrays. This can be achieved using the following code:

case 'DELETE_A_TODO':
    return state.map(category => {
        return {
            ...category,
            todos: category.todos.filter(
                todo => todo.id !== action.payload.id
            )
        };
    });

Hope that helps.

Upvotes: 1

Pengson
Pengson

Reputation: 875

Replace

state.forEach(category => {
    return category.todos.filter(
        todo => todo.id !== action.payload.id
    );
});

with:


return state.map(category => {
  return {
    ...category,
    todos: category.todos.filter(todo => todo.id !== action.payload.id)
  }
})

Upvotes: 2

Related Questions