DEV Community

Cover image for Todo using useReducer in React
Touhidul Shawan
Touhidul Shawan

Posted on

Todo using useReducer in React

What is React?

React is a javaScript library that use to build our user interface for applications.

What is useReducer

useReducer is a react hook that is used to implement our complex state of applications.

In this simple todo app I have used react with typescript template

Step 1:

First of all, create a project called todo using create-react-app
npx create-react-app todo --template typescript
After creating the project cd to todo folder then run
npm start or yarn start

Step 2:

  1. Create two folder called containers , components
  2. Create a file called Todos.tsx in containers
  3. Import Todos.tsx file in App.tsx and use it
import * as React from "react";
import Todos from "./container/Todos";

const App: React.FC = () => {
  return <Todos />;
};
export default App;

Enter fullscreen mode Exit fullscreen mode
  1. Create an interface that will represent the blueprint of a todo item in Todos.tsx file
export interface TodosProps {
  id: string;
  todoName: string;
  isComplete: boolean;
}

Enter fullscreen mode Exit fullscreen mode
  1. Create interfaces for various activities of the app like add todo item, remove todo item and toggle the todo to check that todo item is completed or not
interface AddTodoAction {
  type: "ADD_TODO";
  payload: { name: string };
}

interface ModifyTodoAction {
  type: "TOGGLE_TODO" | "DELETE_TODO";
  payload: { id: string };
}
Enter fullscreen mode Exit fullscreen mode
  1. action type
export type TodoAction = AddTodoAction | ModifyTodoAction
Enter fullscreen mode Exit fullscreen mode
  1. Create a reducer function called todoReducer that is used with useReducer to control the state with action.
const todoReducer = (todos: Array<TodosProps>, action: TodoAction) => {
  switch (action.type) {
    case "ADD_TODO":
      return [...todos, newTodo(action.payload.name)];
    case "TOGGLE_TODO":
      return todos.map((todo) => {
        if (todo.id === action.payload.id) {
          return { ...todo, isComplete: !todo.isComplete };
        }
        return todo;
      });
    case "DELETE_TODO":
      return todos.filter((todo) => todo.id !== action.payload.id);
    default:
      return todos;
  }
};
Enter fullscreen mode Exit fullscreen mode
  1. Create a new todo item structure with this function
const newTodo = (todoName: string): TodosProps => {
  return { id: uuidv4(), todoName: todoName, isComplete: false };
};
Enter fullscreen mode Exit fullscreen mode
  1. Todos.tsx
import * as React from "react";
import { useReducer } from "react";
import { v4 as uuidv4 } from "uuid";
import Todo from "../components/Todo";
import TodoInput from "../components/TodoInput";

export interface TodosProps {
  id: string;
  todoName: string;
  isComplete: boolean;
}

interface AddTodoAction {
  type: "ADD_TODO";
  payload: { name: string };
}

interface ModifyTodoAction {
  type: "TOGGLE_TODO" | "DELETE_TODO";
  payload: { id: string };
}

export type TodoAction = AddTodoAction | ModifyTodoAction;

const todoReducer = (todos: Array<TodosProps>, action: TodoAction) => {
  switch (action.type) {
    case "ADD_TODO":
      return [...todos, newTodo(action.payload.name)];
    case "TOGGLE_TODO":
      return todos.map((todo) => {
        if (todo.id === action.payload.id) {
          return { ...todo, isComplete: !todo.isComplete };
        }
        return todo;
      });
    case "DELETE_TODO":
      return todos.filter((todo) => todo.id !== action.payload.id);
    default:
      return todos;
  }
};

const newTodo = (todoName: string): TodosProps => {
  return { id: uuidv4(), todoName: todoName, isComplete: false };
};

const Todos: React.FC = () => {
  const [todos, dispatch] = useReducer(todoReducer, []);

  const renderTodos = todos.map((todo) => (
    <Todo
      key={todo.id}
      id={todo.id}
      todoName={todo.todoName}
      isComplete={todo.isComplete}
      dispatch={dispatch}
    />
  ));

  console.log(todos);

  return (
    <div>
      <TodoInput dispatch={dispatch} />
      {renderTodos}
    </div>
  );
};

export default Todos;

Enter fullscreen mode Exit fullscreen mode
  1. Create a file called TodoInput.tsx in components folder

TodoInput.tsx

This component is responsible for rendering a form with an input field and a submit button

import * as React from "react";
import { useState } from "react";
import { TodoAction } from "../container/Todos";

interface TodoInputProps {
  dispatch: React.Dispatch<TodoAction>;
}

const TodoInput: React.FC<TodoInputProps> = ({ dispatch }) => {
  const [todoName, setTodoName] = useState("");

  const handleChange = (evt: React.FormEvent<HTMLInputElement>) => {
    setTodoName(evt.currentTarget.value);
  };
  const handleSubmit = (evt: React.FormEvent) => {
    evt.preventDefault();
    dispatch({ type: "ADD_TODO", payload: { name: todoName } });
    setTodoName("");
  };
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Type your todo...."
        value={todoName}
        onChange={handleChange}
      />
      <button type="submit">Add Todo</button>
    </form>
  );
};

export default TodoInput;

Enter fullscreen mode Exit fullscreen mode
  1. Display all todo item in Todo.tsx file in components folder
import * as React from "react";
import { TodosProps, TodoAction } from "../container/Todos";

export interface Props extends TodosProps {
  dispatch: React.Dispatch<TodoAction>;
}

const Todo: React.FC<Props> = ({ dispatch, id, isComplete, todoName }) => {
  const handleDelete = (id: string) => {
    dispatch({
      type: "DELETE_TODO",
      payload: { id: id },
    });
  };
  const handleToggle = (id: string) => {
    dispatch({
      type: "TOGGLE_TODO",
      payload: { id: id },
    });
  };
  return (
    <div>
      <div>
        <p style={{ textDecoration: `${isComplete ? "line-through" : ""}` }}>
          {todoName}
        </p>
      </div>
      <div>
        <button onClick={() => handleToggle(id)}>Toggle</button>
        <button onClick={() => handleDelete(id)}>Delete</button>
      </div>
    </div>
  );
};

export default Todo;

Enter fullscreen mode Exit fullscreen mode

Top comments (0)