DEV Community

Bipin Bhandari
Bipin Bhandari

Posted on

🧑‍💻Todo using NextJS 13+, ( useContext, useReducer, TypeScript ).

This is a TODO List app made using NextJs 13+ (latest version).

The Final Todo App :

Image description

Tools used are :

  1. useContext
  2. useReducer
  3. TypeScript

All Steps

Step 1 : First Setup the Nextjs 13 using :

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

and setup the nextjs app following all commands.

Step 2 : Make a folder structure like this :

Image description

Step 3 : First make a reducer :

(NewReducer.ts)

export interface Todo {
  id?: number;
  todoName: string;
  completed: boolean;
}

export interface StateType {
  todos: Todo[];
}

export interface ActionType {
  type: "CREATE_TODO" | "DELETE_TODO" | "TOOGLE_TODO";
  payload: Todo;
}

export const INITIAL_STATE = {
  todos: [],
};

export const reducer = (state: StateType, action: ActionType) => {
  switch (action.type) {
    case "CREATE_TODO":
      return {
        ...state,
        todos: [...state.todos, action.payload],
      };

    case "DELETE_TODO":
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload.id),
      };

    case "TOOGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((todo) => {
          if (todo.id === action.payload.id) {
            return { ...todo, completed: !todo.completed };
          }
          return todo;
        }),
      };

    default:
      return state;
  }
};

Enter fullscreen mode Exit fullscreen mode

Step 4 : Create a context

(NewContext.tsx)

"use client";

import React, { createContext, useContext, useReducer } from "react";
import {
  StateType,
  ActionType,
  INITIAL_STATE,
  reducer,
} from "@/reducers/NewReducer";

interface NewContextProps {
  children: React.ReactNode;
}

interface NewContextValue {
  state: StateType;
  dispatch: React.Dispatch<ActionType>;
}

const NewContext = createContext<NewContextValue>({
  state: INITIAL_STATE,
  dispatch: () => {},
});

export const useNewContext = () => useContext(NewContext);

export const NewContextProvider: React.FC<NewContextProps> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
  return (
    <NewContext.Provider value={{ state, dispatch }}>
      {children}
    </NewContext.Provider>
  );
};

Enter fullscreen mode Exit fullscreen mode

Step 5 :

(Todo.tsx)


"use client";

import { useNewContext } from "@/context/NewContext";
import { useState } from "react";

interface Todo {
  id?: number;
  todoName: string;
  completed: boolean;
}

const Todo = () => {
  const { state, dispatch } = useNewContext();

  const [todo, setTodo] = useState<Todo>({
    todoName: "",
    completed: false,
  });

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodo({ ...todo, todoName: e.target.value });
  };

  const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const newTodo = {
      id: Date.now(),
      todoName: todo.todoName,
      completed: false,
    };
    dispatch({ type: "CREATE_TODO", payload: newTodo });
    setTodo({ todoName: "", completed: false });
  };

  return (
    <div className="flex flex-col">
      <form
        onSubmit={handleFormSubmit}
        className="w-[300px] m-auto flex flex-col gap-2"
      >
        <input
          value={todo.todoName}
          onChange={handleInputChange}
          placeholder="Enter a Todo"
          className="border-[1.5px] border-black p-3 m-2"
        />
        <button type="submit" className="btn">
          Create Todo
        </button>
      </form>
      <h1 className="underline mt-9 mb-3 font-bold text-xl m-auto">
        Todos List
      </h1>

      {state.todos.length > 0 ? (
        state.todos.map((todo, index) => (
          <div
            key={todo.id}
            className="flex justify-between shadow-md bg-slate-100 w-[300px] m-auto my-3 p-4 text-xl font-bold"
          >
            <h1
              onClick={() => dispatch({ type: "TOOGLE_TODO", payload: todo })}
              className={`${
                todo.completed ? "line-through opacity-60" : "no-underline"
              } cursor-pointer`}
            >
              {index + 1}. {todo.todoName}
            </h1>
            <button
              onClick={() =>
                dispatch({
                  type: "DELETE_TODO",
                  payload: todo,
                })
              }
              className="text-red-500 border-[2.5px] border-red-500 px-2"
            >
              Delete
            </button>
          </div>
        ))
      ) : (
        <h1 className="text-center font-bold text-red-600">No Todos :(</h1>
      )}
    </div>
  );
};

export default Todo;

Enter fullscreen mode Exit fullscreen mode

Any questions related to this code feel free to ask :)

Top comments (0)