DEV Community

Bipin Bhandari
Bipin Bhandari

Posted on

1

🧑‍💻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 :)

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

đź‘‹ Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay