Reputation: 341
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
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
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
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