1

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

Mario
  • 4,784
  • 3
  • 34
  • 50
  • 2
    It sounds like you're looking for something like Redux' [`combineReducers`](https://redux.js.org/api/combinereducers) function. – zzzzBov Dec 03 '19 at 21:51

0 Answers0