Paramclasses Academy - Blog

Tutorials Details

←
Post 24 of 29
→

Project 1 — Building an Interactive Todo Application in react.js

Project 1 — Building an Interactive Todo Application in react.js

October 30, 2026

1. Introduction

In this tutorial, we will build a complete Interactive Todo Application using React.js.

A Todo Application helps users manage their daily tasks. Users can add new tasks, edit existing tasks, delete tasks, mark tasks as completed, and filter tasks based on their status.

This project will help you understand how React concepts work together in a real-world application.

Technologies Used:

  • React.js

  • JavaScript ES6+

  • HTML5

  • CSS3

  • Vite

  • Browser Local Storage

2. Learning Objectives

After completing this tutorial, you will learn:

  1. How to create a React project using Vite.

  2. How to divide an application into reusable components.

  3. How to manage application data using useState.

  4. How to add, edit, and delete tasks.

  5. How to mark tasks as completed.

  6. How to filter tasks.

  7. How to calculate task statistics.

  8. How to save tasks in Local Storage.

  9. How to build a responsive user interface.

3. Project Features

Our Todo Application will include the following features:

Feature Description
Add Task Create a new task
Edit Task Update an existing task
Delete Task Remove a task
Complete Task Mark a task as completed
Active Filter Show pending tasks
Completed Filter Show completed tasks
All Tasks Display every task
Task Statistics Show total, active, and completed counts
Local Storage Save tasks in the browser
Responsive UI Support mobile and desktop screens

4. Create React Project

First, open your terminal and execute:

npm create vite@latest todo-app -- --template react

Move into the project directory:

cd todo-app

Install dependencies:

npm install

Start the development server:

npm run dev

Open the local URL displayed in your terminal.

5. Project Folder Structure

Organize your project like this:

todo-app/
│
├── public/
│
├── src/
│   ├── components/
│   │   ├── TodoForm.jsx
│   │   ├── TodoList.jsx
│   │   └── TodoItem.jsx
│   │
│   ├── App.jsx
│   ├── App.css
│   ├── index.css
│   └── main.jsx
│
├── package.json
└── index.html

Each component has a specific responsibility:

  • TodoForm: Takes task input from the user.

  • TodoList: Displays the list of tasks.

  • TodoItem: Displays and manages an individual task.

  • App: Manages the main application state and logic.

6. Build the Todo Application

Step 1: Create TodoForm Component

Create src/components/TodoForm.jsx.

import { useState } from "react";

function TodoForm({ onAddTask }) {
  const [task, setTask] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();

    const trimmedTask = task.trim();

    if (!trimmedTask) return;

    onAddTask(trimmedTask);
    setTask("");
  };

  return (
    <form className="todo-form" onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Enter your task..."
        value={task}
        onChange={(e) => setTask(e.target.value)}
      />

      <button type="submit">
        Add Task
      </button>
    </form>
  );
}

export default TodoForm;

Explanation:

  • useState() stores the input value.

  • onChange updates the task whenever the user types.

  • preventDefault() prevents the browser from reloading the page.

  • trim() removes unnecessary spaces.

  • onAddTask() sends the new task to the parent component.

Step 2: Create TodoItem Component

Create src/components/TodoItem.jsx.

function TodoItem({
  todo,
  onToggle,
  onDelete,
  onEdit
}) {
  return (
    <div className={`todo-item ${todo.completed ? "completed" : ""}`}>

      <label className="todo-content">
        <input
          type="checkbox"
          checked={todo.completed}
          onChange={() => onToggle(todo.id)}
        />

        <span>{todo.text}</span>
      </label>

      <div className="todo-actions">
        <button
          className="edit-btn"
          onClick={() => onEdit(todo)}
        >
          Edit
        </button>

        <button
          className="delete-btn"
          onClick={() => onDelete(todo.id)}
        >
          Delete
        </button>
      </div>

    </div>
  );
}

export default TodoItem;

Explanation:

  • todo contains the current task information.

  • onToggle() changes the completion status.

  • onEdit() sends the selected task to the parent component.

  • onDelete() removes the selected task.

  • Conditional CSS classes visually identify completed tasks.

Step 3: Create TodoList Component

Create src/components/TodoList.jsx.

import TodoItem from "./TodoItem";

function TodoList({
  todos,
  onToggle,
  onDelete,
  onEdit
}) {
  if (todos.length === 0) {
    return (
      <p className="empty-message">
        No tasks found. Add a new task!
      </p>
    );
  }

  return (
    <div className="todo-list">
      {todos.map((todo) => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
          onDelete={onDelete}
          onEdit={onEdit}
        />
      ))}
    </div>
  );
}

export default TodoList;

Explanation:

  • map() displays every task.

  • key provides a unique identity to each list item.

  • Props pass task information and event handlers to TodoItem.

  • Empty-state message appears when there are no matching tasks.

Step 4: Create Main App Component

Open src/App.jsx.

import { useEffect, useState } from "react";

import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";

import "./App.css";

function App() {

  const [todos, setTodos] = useState(() => {
    try {
      const savedTodos = localStorage.getItem("my-todos");

      return savedTodos ? JSON.parse(savedTodos) : [];
    } catch {
      return [];
    }
  });

  const [filter, setFilter] = useState("all");
  const [editingId, setEditingId] = useState(null);
  const [editText, setEditText] = useState("");

  // Save tasks in Local Storage
  useEffect(() => {
    localStorage.setItem("my-todos", JSON.stringify(todos));
  }, [todos]);

  // Add Task
  const addTask = (text) => {
    const newTodo = {
      id: crypto.randomUUID(),
      text,
      completed: false
    };

    setTodos((prevTodos) => [
      ...prevTodos,
      newTodo
    ]);
  };

  // Toggle Task
  const toggleTask = (id) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    );
  };

  // Delete Task
  const deleteTask = (id) => {
    setTodos((prevTodos) =>
      prevTodos.filter((todo) => todo.id !== id)
    );

    if (editingId === id) {
      setEditingId(null);
      setEditText("");
    }
  };

  // Start Editing
  const startEdit = (todo) => {
    setEditingId(todo.id);
    setEditText(todo.text);
  };

  // Update Task
  const updateTask = (id) => {
    const updatedText = editText.trim();

    if (!updatedText) return;

    setTodos((prevTodos) =>
      prevTodos.map((todo) =>
        todo.id === id
          ? { ...todo, text: updatedText }
          : todo
      )
    );

    setEditingId(null);
    setEditText("");
  };

  // Filter Tasks
  const filteredTodos = todos.filter((todo) => {
    if (filter === "active") {
      return !todo.completed;
    }

    if (filter === "completed") {
      return todo.completed;
    }

    return true;
  });

  const totalTasks = todos.length;

  const completedTasks = todos.filter(
    (todo) => todo.completed
  ).length;

  const activeTasks = totalTasks - completedTasks;

  // Clear Completed Tasks
  const clearCompleted = () => {
    setTodos((prevTodos) =>
      prevTodos.filter((todo) => !todo.completed)
    );
  };

  return (
    <div className="app-container">

      <div className="todo-card">

        <header className="todo-header">
          <h1>My Todo App</h1>
          <p>Organize your day, one task at a time.</p>
        </header>

        <TodoForm onAddTask={addTask} />

        <div className="task-stats">

          <div>
            <strong>{totalTasks}</strong>
            <span>Total</span>
          </div>

          <div>
            <strong>{activeTasks}</strong>
            <span>Active</span>
          </div>

          <div>
            <strong>{completedTasks}</strong>
            <span>Completed</span>
          </div>

        </div>

        <div className="filter-buttons">

          <button
            className={filter === "all" ? "active-filter" : ""}
            onClick={() => setFilter("all")}
          >
            All
          </button>

          <button
            className={filter === "active" ? "active-filter" : ""}
            onClick={() => setFilter("active")}
          >
            Active
          </button>

          <button
            className={filter === "completed" ? "active-filter" : ""}
            onClick={() => setFilter("completed")}
          >
            Completed
          </button>

        </div>

        <TodoList
          todos={filteredTodos}
          onToggle={toggleTask}
          onDelete={deleteTask}
          onEdit={startEdit}
        />

        {editingId !== null && (
          <form
            className="edit-form"
            onSubmit={(e) => {
              e.preventDefault();
              updateTask(editingId);
            }}
          >
            <input
              type="text"
              value={editText}
              onChange={(e) => setEditText(e.target.value)}
              autoFocus
            />

            <button type="submit">Save</button>

            <button
              type="button"
              onClick={() => {
                setEditingId(null);
                setEditText("");
              }}
            >
              Cancel
            </button>
          </form>
        )}

        {completedTasks > 0 && (
          <button
            className="clear-btn"
            onClick={clearCompleted}
          >
            Clear Completed Tasks
          </button>
        )}

      </div>

    </div>
  );
}

export default App;

Important Concepts Used:

  1. useState: Stores tasks, filter selection, and editing information.

  2. useEffect: Saves updated tasks in Local Storage.

  3. map(): Updates task information and displays tasks.

  4. filter(): Selects tasks based on their completion status.

  5. JSON.stringify(): Converts JavaScript data into JSON text.

  6. JSON.parse(): Converts saved JSON text back into JavaScript data.

  7. crypto.randomUUID(): Generates a unique ID for each task.

Step 5: Add CSS Styling

Open src/App.css.

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: Arial, sans-serif;
  background: #eef2ff;
}

.app-container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 50px 16px;
}

.todo-card {
  width: 100%;
  max-width: 650px;
  background: white;
  padding: 30px;
  border-radius: 16px;
  box-shadow: 0 10px 35px rgba(0, 0, 0, 0.10);
}

.todo-header {
  text-align: center;
  margin-bottom: 25px;
}

.todo-header h1 {
  margin-bottom: 8px;
  color: #312e81;
}

.todo-header p {
  color: #64748b;
}

.todo-form,
.edit-form {
  display: flex;
  gap: 10px;
}

.todo-form input,
.edit-form input {
  flex: 1;
  min-width: 0;
  padding: 13px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  font-size: 15px;
}

button {
  cursor: pointer;
  border: none;
  border-radius: 8px;
  padding: 11px 15px;
  font-weight: bold;
}

.todo-form button,
.edit-form button[type="submit"] {
  background: #4f46e5;
  color: white;
}

.task-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin: 25px 0;
}

.task-stats div {
  background: #eef2ff;
  padding: 15px 8px;
  border-radius: 10px;
  text-align: center;
}

.task-stats strong,
.task-stats span {
  display: block;
}

.task-stats strong {
  font-size: 23px;
  color: #4338ca;
}

.task-stats span {
  margin-top: 5px;
  color: #64748b;
}

.filter-buttons {
  display: flex;
  gap: 10px;
  margin-bottom: 18px;
}

.filter-buttons button {
  flex: 1;
  background: #e2e8f0;
  color: #334155;
}

.filter-buttons .active-filter {
  background: #4f46e5;
  color: white;
}

.todo-list {
  display: grid;
  gap: 12px;
}

.todo-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 9px;
}

.todo-content {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.todo-item.completed .todo-content span {
  text-decoration: line-through;
  color: #94a3b8;
}

.todo-actions {
  display: flex;
  gap: 7px;
  flex-shrink: 0;
}

.edit-btn {
  background: #fef3c7;
  color: #92400e;
}

.delete-btn {
  background: #fee2e2;
  color: #b91c1c;
}

.empty-message {
  text-align: center;
  padding: 25px 10px;
  color: #64748b;
}

.edit-form {
  margin-top: 18px;
}

.edit-form button[type="button"] {
  background: #e2e8f0;
  color: #334155;
}

.clear-btn {
  width: 100%;
  margin-top: 20px;
  background: #dc2626;
  color: white;
}

@media (max-width: 480px) {
  .todo-card {
    padding: 18px;
  }

  .todo-item {
    align-items: flex-start;
    flex-direction: column;
  }

  .todo-actions {
    width: 100%;
  }

  .todo-actions button {
    flex: 1;
  }

  .edit-form {
    flex-wrap: wrap;
  }
}

Step 6: Check main.jsx

Ensure src/main.jsx contains:

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App.jsx";
import "./index.css";

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

You can keep src/index.css minimal or remove its default Vite styles so they do not conflict with the application CSS.

7. How the Application Works

The complete application follows this flow:

  1. The user enters a task in the input field.

  2. TodoForm sends the task to App through a function prop.

  3. App creates a task object with a unique ID.

  4. React updates the todos state.

  5. TodoList displays the updated task list.

  6. The user can complete, edit, or delete a task.

  7. useEffect saves the updated tasks in Local Storage.

  8. When the browser is reopened, saved tasks are loaded again.

Example Task Object:

{
  id: "unique-task-id",
  text: "Learn React Hooks",
  completed: false
}

8. Common Mistakes and Best Practices

Common Mistakes:

  1. Forgetting to use a unique key when rendering lists.

  2. Directly modifying the state array.

  3. Forgetting to trim user input.

  4. Not handling empty task lists.

  5. Forgetting to save updated tasks in Local Storage.

  6. Using array indexes as task IDs.

  7. Not providing a way to cancel editing.

Best Practices:

  • Use reusable components.

  • Keep state updates immutable.

  • Use unique IDs for tasks.

  • Validate user input.

  • Use functional state updates when the new state depends on the previous state.

  • Use Local Storage for simple browser-based persistence.

  • Keep components focused on their individual responsibilities.

  • Use semantic HTML and accessible labels.

Security Note: Local Storage is browser-side storage. Do not store passwords, authentication secrets, or sensitive personal information in it.

9. Practice Exercises

Try completing these exercises yourself:

  1. Add a task priority feature: Low, Medium, and High.

  2. Add a search box to find tasks by name.

  3. Add a due date to each task.

  4. Display a message when all tasks are completed.

  5. Add a confirmation before deleting a task.

  6. Add a button to delete all tasks.

  7. Display the completion percentage of all tasks.

10. Key Takeaways and Conclusion

In this tutorial, we built an Interactive Todo Application using React.js.

You learned how to:

  • Create a React application using Vite.

  • Build reusable functional components.

  • Manage data using useState.

  • Handle events and forms.

  • Add, edit, delete, and complete tasks.

  • Filter tasks using JavaScript filter().

  • Display task statistics.

  • Save data using Local Storage and useEffect.

This project gives you practical experience with the most important React fundamentals and provides a foundation for building larger applications.

←
Post 24 of 29
→