This is a TODO List app made using NextJs 13+ (latest version).
The Final Todo App :
Tools used are :
- useContext
- useReducer
- TypeScript
All Steps
Step 1 : First Setup the Nextjs 13 using :
npx create-next-app@latest
and setup the nextjs app following all commands.
Step 2 : Make a folder structure like this :
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;
}
};
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>
);
};
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;
Any questions related to this code feel free to ask :)
Top comments (0)