To be productive and to remember all the tasks for the day, the To-Do app is very important. In this article, you will see the whole process of creating a to-do app from scratch. This to-do list app includes functionalities like adding new task, editing the task and deleting the task by clicking on the buttons available.
Output Preview: Let us have a look at how the final output will look like
Prerequisites:
Approach to create a To-Do List App:
Through this app you will be able to add new tasks, change ones you’ve already made, and delete tasks you don’t need anymore. It’s as easy as clicking a button! Plus, we’ll make sure your tasks stay saved, in the local storage.
So, in this project there is separate components like the Header.js which will act like a Navigation bar for the to-do app, then in the index.js file there is the form to add the to-do. There is a separate component to view the todos and that is todos.js.
Steps to create the Project and Install required modules
Step 1: Create the NextJs App using the following command.
npx create-next-app@latest
Configuration which you should follow while creating App :
Step 2: Move to the project folder from Terminal
cd <project_name>
Step 3: Install tailwind
npm i tailwind
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
"tailwind": "^4.0.0"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0"
}
Step 4: Write the following code in different files(The name of the files is mentioned in the first line of each code block)
// pages/index.js import { useState } from 'react'
export default function Home() {
const [todo, setTodo] = useState({ title: "" , desc: "" })
const addTodo = () => {
let todos = localStorage.getItem( "todos" )
if (todos) {
let todosJson = JSON.parse(todos)
if (todosJson.filter(
value => {
return value.title == todo.title
}
).length > 0) {
alert( "Todo with this title already exists" )
}
else {
todosJson.push(todo)
localStorage
.setItem( "todos" , JSON.stringify(todosJson))
alert( "Todo has been added" )
setTodo({ title: "" , desc: "" })
}
}
else {
localStorage.setItem( "todos" , JSON.stringify([todo]))
}
}
const onChange = (e) => {
setTodo(
{
...todo,
[e.target.name]: e.target.value
}
)
console.log(todo)
}
return (
<div className= " text-3xl" >
<section className= "text-gray-600 body-font" >
<div className= "container px-5 py-24
mx-auto flex flex-wrap items-center" >
<div className= "rounded-lg p-8 flex flex-col
md:ml-auto w-full mt-10 md:mt-0 bg-slate-100 " >
<h2 className= "text-gray-900 text-lg
font-medium title-font mb-5" >
Add a Todo
</h2>
<div className= "relative mb-4" >
<label for = "title"
className= "leading-7 text-sm
text-gray-600" >
Todo Title
</label>
<input onChange={onChange} value={todo.title}
type= "text" id= "title" name= "title"
className= "w-full bg-white rounded border border-gray-300
focus:border-green-800 focus:ring-2
focus:ring-green-200 text-base outline-none
text-gray-700 py-1 px-3
leading-8 transition-colors duration-200 ease-in-out"
autoComplete= 'false' />
</div>
<div className= "relative mb-4" >
<label for = "desc" className= "leading-7 text-sm text-gray-600" >
Todo Description</label>
<input onChange={onChange} value={todo.desc}
type= "text" id= "desc" name= "desc"
className= "w-full bg-white rounded border
border-gray-300 focus:border-green-800
focus:ring-2 focus:ring-green-200 text-base
outline-none text-gray-700 py-1 px-3
leading-8 transition-colors duration-200 ease-in-out" autoComplete= 'false' />
</div>
<button onClick={addTodo} className= "text-white
bg-green-800 border-0 py-2 px-8
focus:outline-none w-fit hover:bg-green-600
rounded text-lg" >Add Todo</button>
</div>
</div>
</section>
</div>
)
} |
// _app.js import Header from '@/components/Header'
import '@/styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<>
<Header />
<div className= "container mx-auto min-h-screen" >
<Component {...pageProps} />
</div>
</>
)
} |
// pages/todos.js import React, { useEffect, useState } from "react" ;
const todos = () => { const [todos, setTodos] = useState([]);
useEffect(() => {
let todos = localStorage.getItem( "todos" );
if (!todos) {
localStorage.setItem( "todos" , JSON.stringify([]));
} else {
setTodos(JSON.parse(todos));
}
}, []);
const deleteTodo = (title) => {
let newTodos = todos.filter((item) => {
return item.title != title;
});
localStorage.setItem( "todos" , JSON.stringify(newTodos));
setTodos(newTodos);
};
return (
<section className= "text-gray-600 body-font" >
<div className= "container px-5 py-24 mx-auto" >
<div className= "flex flex-col text-center w-full mb-20" >
<h1 className= "text-4xl font-medium
title-font mb-4 text-gray-900" >
Your Todos
</h1>
{todos.length == 0 && (
<p className= "mx-auto leading-relaxed text-base" >
Please add a todo by going to the homepage
</p>
)}
</div>
{
todos.length > 0 && (
<div className= "w-full" >
<div className= "relative overflow-x-auto
shadow-md sm:rounded-lg" >
<table className= "w-full text-sm text-left
rtl:text-right text-gray-500 " >
<thead className= "text-xs text-gray-700
uppercase bg-gray-50 " >
<tr>
<th scope= "col"
className= "px-6 py-3" >
Title
</th>
<th scope= "col"
className= "px-6 py-3" >
Description
</th>
<th scope= "col"
className= "px-6 py-3" >
Action
</th>
</tr>
</thead>
<tbody>
{todos &&
todos.map((item, index) => {
return (
<tr
className= "odd:bg-white
even:bg-gray-50
border-b " key={index}>
<th
scope= "row"
className= "px-6 py-4 font-medium
text-gray-900 whitespace-nowrap"
>
{item.title}
</th>
<td className= "px-6 py-4" >{item.desc}</td>
<td className= "px-6 py-4" >
<span className= "inline-flex" >
<a
className= " cursor-pointer font-medium
border-2
border-red-500 rounded-md p-1
hover:bg-red-500
hover:text-white"
onClick={() => {
deleteTodo(item.title);
}}
>
<svg
viewBox= "0 0 30 30"
width= "20px"
height= "20px"
>
{ " " }
<path d= "M 14.984375 2.4863281 A 1.0001
1.0001 0 0 0 14 3.5 L 14 4 L 8.5 4 A
1.0001 1.0001 0 0 0 7.4863281 5 L 6 5 A
1.0001
1.0001 0 1 0 6 7 L 24 7 A 1.0001 1.0001
0 1
0 24 5 L 22.513672 5 A 1.0001 1.0001
0 0 0
21.5 4 L 16 4 L 16 3.5 A 1.0001 1.0001
0 0
0 14.984375 2.4863281 z M 6 9 L
7.7929688
24.234375 C 7.9109687 25.241375
8.7633438
26 9.7773438 26 L 20.222656 26 C
21.236656
26 22.088031 25.241375 22.207031
24.234375
L 24 9 L 6 9 z" />
</svg>
</a>
<a
className= "ml-2 cursor-pointer
border-2
border-green-500 rounded-md p-1
hover:bg-green-500
hover:text-white"
href={`/edit/${item.title}`}
>
<svg
viewBox= "0 0 50 50"
width= "20px"
height= "20px"
>
<path d= "M 43.125 2 C 41.878906 2
40.636719
2.488281 39.6875 3.4375 L 38.875
4.25 L
45.75 11.125 C 45.746094 11.128906
46.5625
10.3125 46.5625 10.3125 C 48.464844
8.410156 48.460938 5.335938 46.5625
3.4375
C 45.609375 2.488281 44.371094 2
43.125 2 Z
M 37.34375 6.03125 C 37.117188
6.0625
36.90625 6.175781 36.75 6.34375 L
4.3125
38.8125 C 4.183594 38.929688 4.085938
39.082031 4.03125 39.25 L 2.03125
46.75 C
1.941406 47.09375 2.042969 47.457031
2.292969 47.707031 C 2.542969
47.957031
2.90625 48.058594 3.25 47.96875 L
10.75
45.96875 C 10.917969 45.914063
11.070313
45.816406 11.1875 45.6875 L 43.65625
13.25
C 44.054688 12.863281 44.058594
12.226563
43.671875 11.828125 C 43.285156
11.429688
42.648438 11.425781 42.25 11.8125 L
9.96875
44.09375 L 5.90625 40.03125 L 38.1875
7.75
C 38.488281 7.460938 38.578125
7.011719
38.410156 6.628906 C 38.242188
6.246094
37.855469 6.007813 37.4375 6.03125
C 37.40625 6.03125 37.375 6.03125
37.34375
6.03125 Z" />
</svg>
</a>
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)
}
</div>
</section>
);
}; export default todos;
|
// pages/_document.js import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang= "en" >
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
} |
// pages/edit/[title].js import React from 'react'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
const Edit = () => { const router = useRouter()
const { title } = router.query
const [todo, setTodo] = useState({ title: "" , desc: "" })
const updateTodo = () => {
let todos = localStorage.getItem( "todos" )
if (todos) {
let todosJson = JSON.parse(todos)
if (todosJson.filter(value => {
return value.title == title }).length > 0) {
let index = todosJson.findIndex(value => {
return value.title == title })
todosJson[index].title = todo.title
todosJson[index].desc = todo.desc
localStorage.setItem( "todos" , JSON.stringify(todosJson))
alert( "Todo has been updated" )
}
else {
alert( "Todo does not exist" )
}
}
else {
localStorage.setItem( "todos" , JSON.stringify([todo]))
}
}
useEffect(() => {
let todos = localStorage.getItem( "todos" )
if (todos) {
let todosJson = JSON.parse(todos)
let ftodo = todosJson.filter(e => title == e.title)
console.log(ftodo)
if (ftodo.length > 0) {
setTodo(ftodo[0])
}
}
}, [router.isReady])
const onChange = (e) => {
setTodo({ ...todo, [e.target.name]: e.target.value })
console.log(todo)
}
return (
<div className= "my-2 text-3xl" >
<section className= "text-gray-600 body-font" >
<div className= "container px-5 py-24
mx-auto flex flex-wrap
items-center" >
<div className= "bg-gray-100 rounded-lg
p-8 flex flex-col
md:ml-auto w-full
mt-10 md:mt-0" >
<h2 className= "text-gray-900 text-lg
font-medium title-font mb-5" >
Update a Todo
</h2>
<div className= "relative mb-4" >
<label for = "title"
className= "leading-7 text-sm
text-gray-600" >
Todo Title</label>
<input onChange={onChange}
value={todo.title}
type= "text"
id= "title"
name= "title"
className= "w-full bg-white rounded
border border-gray-300
focus:border-green-500
focus:ring-2
focus:ring-indigo-200
text-base outline-none
text-gray-700 py-1 px-3
leading-8 transition-colors
duration-200 ease-in-out" />
</div>
<div className= "relative mb-4" >
<label for = "desc"
className= "leading-7 text-sm
text-gray-600" >
Todo Text
</label>
<input onChange={onChange}
value={todo.desc}
type= "text"
id= "desc"
name= "desc"
className= "w-full bg-white
rounded border border-gray-300
focus:border-green-500
focus:ring-2
focus:ring-indigo-200
text-base outline-none
text-gray-700 py-1 px-3
leading-8 transition-colors
duration-200 ease-in-out" />
</div>
<button onClick={updateTodo}
className= "text-white bg-green-500
border-0 py-2 px-8
focus:outline-none w-fit
hover:bg-green-600 rounded
text-lg" >Update Todo</button>
<p className= "text-xs text-gray-500 mt-3" >
The best todo list app out there!
</p>
</div>
</div>
</section>
</div>
)
} export default Edit
|
// components/Header.js import Link from 'next/link'
import React from 'react'
const Header = () => { return (
<header className= "text-gray-600 body-font" >
<div className= "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" >
<h1 className= "flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0" >
<span className= "ml-3 text-2xl text-green-800" >GfG </span>
<span className= 'text-black' >TodoList</span>
</h1>
<nav className= "md:ml-auto flex flex-wrap items-center text-base justify-center" >
<Link href={ "/" } className= "mr-5 hover:text-gray-900" >Add ➕</Link>
<Link href={ "/todos" } className= "mr-5 hover:text-white hover:bg-green-800 border-2 p-2
text-black border-green-800 rounded " >My Todos ???? </Link>
</nav>
</div>
<hr className= "border-t-2 border-gray-200" />
</header>
)
} export default Header
|
/*global.css */ @tailwind base; @tailwind components; @tailwind utilities; |
Run your app by executing below command.
npm run dev