Open In App

Create a To-Do List App with Next JS and Tailwind

Last Updated : 27 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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

Screenshot-2024-02-20-104501

Desktop view

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 :

Screenshot-2024-02-20-105418

Step 2: Move to the project folder from Terminal

cd <project_name>

Step 3: Install tailwind

npm i tailwind

Project Structure:

todo2

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)

Javascript




// 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>
    )
}


Javascript




// _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>
        </>
    )
}


Javascript




// 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
                                                                        xmlns="http://www.w3.org/2000/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
                                                                        xmlns="http://www.w3.org/2000/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;


Javascript




// 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>
    )
}


Javascript




// 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


Javascript




// 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


CSS




/*global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;


Run your app by executing below command.

npm run dev

Output:

todos

Todo App UI (Mobile view)



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads