I am trying to get use to react hooks specifically useReduce
, for this purpose I am making a todoList
example in which I would like to use one reduce to manage the state for actions like adding, editing, updating, deleting and changing the completion status of tasks and in another reduce manage filters that allow me to list all the todos
or just the completed and uncompleted.
Is it possible to manage the state through more than one reducer using useReducer hook?
Right now, I have had to mix the reducers, having the following logic
import React, { useReducer, useState } from "react";
import uuid from "uuid/v4";
import Form from "../Form/Form";
import List from "../List/List";
import Filter from "../Filter/Filter";
const todoReducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return applyAddTodo(state, action);
case "DELETE_TODO":
return applyDeleteTodo(state, action);
case "EDIT_TODO":
return applyEditTodo(state, action);
case "CANCEL_EDIT_TODO":
return applyCancelEditTodo(state, action);
case "UPDATE_TODO":
return applyUpdateTodo(state, action);
case "TOGGLE_COMPLETED_TODO":
return applyToggleCompletedTodo(state, action);
case "COMPLETED": // <- from here this should be in another reducer
return applyFilterCompleted(state);
case "UN_COMPLETED":
return applyFilterUncompleted(state);
case "ALL":
default:
return [...state];
}
};
const todoFilter = (state, action) => {
switch (action.type) {
case "COMPLETED":
return applyFilterCompleted(state, action);
case "UN_COMPLETED":
return applyFilterUncompleted(state, action);
case "ALL":
default:
return [...state];
}
};
const applyAddTodo = (state, action) => [
...state,
{ id: uuid(), task: action.payload.task, completed: false, isEditing: false }
];
const applyDeleteTodo = (state, action) =>
state.filter(todo => todo.id !== action.payload.id);
const applyEditTodo = (state, action) =>
state.map(todo =>
todo.id === action.payload.id ? { ...todo, isEditing: true } : { ...todo }
);
const applyCancelEditTodo = (state, action) =>
state.map(todo =>
todo.id === action.payload.id ? { ...todo, isEditing: false } : { ...todo }
);
const applyUpdateTodo = (state, action) =>
state.map(todo =>
todo.id === action.payload.id
? { ...todo, task: action.payload.task, isEditing: false }
: { ...todo }
);
const applyToggleCompletedTodo = (state, action) =>
state.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: { ...todo }
);
const applyFilterCompleted = state => state.filter(todo => todo.completed);
const applyFilterUncompleted = state => state.filter(todo => !todo.completed);
const INITIAL_STATE = [
{
id: uuid(),
task: "Learn react",
dateCreated: Date.now(),
completed: false,
isEditing: false
},
{
id: uuid(),
task: "Learn react hooks",
dateCreated: Date.now(),
completed: true,
isEditing: false
}
];
const App = () => {
const [state, dispatch] = useReducer(todoReducer, INITIAL_STATE);
const [filter, setFilter] = useState("ALL");
const addTodoHandler = task =>
dispatch({ type: "ADD_TODO", payload: { task } });
const editTodoHandler = id =>
dispatch({ type: "EDIT_TODO", payload: { id } });
const updateTodoHandler = (id, task) =>
dispatch({ type: "UPDATE_TODO", payload: { id, task } });
const cancelEditTodoHandler = id =>
dispatch({ type: "CANCEL_EDIT_TODO", payload: { id } });
const deleteTodoHandler = id =>
dispatch({ type: "DELETE_TODO", payload: { id } });
const toggleCompletedTodoHandler = id =>
dispatch({ type: "TOGGLE_COMPLETED_TODO", payload: { id } });
const filterHandler = filter => setFilter(filter);
return (
<div>
<Form onAddTodo={addTodoHandler} />
{state.length > 0 && (
<div>
<List
todos={todoFilter(state, { type: filter })}
onEditTodo={editTodoHandler}
onUpdateTodo={updateTodoHandler}
onCancelEditTodo={cancelEditTodoHandler}
onDeleteTodo={deleteTodoHandler}
onToggleCompletedTodo={toggleCompletedTodoHandler}
/>
<Filter onFilter={filterHandler} />
</div>
)}
</div>
);
};
export default App;
Right now the reduce todoFilter
I use it as a helper to list the todos
Update 1
I got this solution
Todo reducer
const todoReducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return applyAddTodo(state, action);
case "DELETE_TODO":
return applyDeleteTodo(state, action);
case "EDIT_TODO":
return applyEditTodo(state, action);
case "CANCEL_EDIT_TODO":
return applyCancelEditTodo(state, action);
case "UPDATE_TODO":
return applyUpdateTodo(state, action);
case "TOGGLE_COMPLETED_TODO":
return applyToggleCompletedTodo(state, action);
default:
return state;
}
};
Filter reducer
const filterReducer = (state, action) => {
switch (action.type) {
case "COMPLETED":
return applyFilterCompleted(state, action);
case "UN_COMPLETED":
return applyFilterUncompleted(state, action);
case "ALL":
default:
return [...state];
}
};
Main reducer
const reducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
case "DELETE_TODO":
case "EDIT_TODO":
case "UPDATE_TODO":
case "CANCEL_EDIT_TODO":
case "TOGGLE_COMPLETED_TODO":
return todoReducer(state, action);
case "COMPLETED":
case "UN_COMPLETED":
case "ALL":
return filterReducer(state, action);
default:
throw new Error();
}
};
This works, but I don't like it for the reason of having to repeat the action types in the main and secondary reducers, eventually some action type will be omitted and the application will fail.
Any smarter solution?
Thanks for your comments