Project: To-Do App with localStorage

📚 Lesson 22 of 22  •  ⏱ 30 min read  •  Project

Build a complete to-do app using everything you've learned — DOM manipulation, events, arrays, closures, and localStorage. Tasks persist between page reloads.

HTML Structure

HTML
<div class="todo-app">
  <h1>My Tasks</h1>

  <form id="todo-form">
    <input type="text" id="todo-input" placeholder="Add a task..." required>
    <button type="submit">Add</button>
  </form>

  <div class="filters">
    <button class="filter-btn active" data-filter="all">All</button>
    <button class="filter-btn" data-filter="active">Active</button>
    <button class="filter-btn" data-filter="done">Done</button>
  </div>

  <ul id="todo-list"></ul>
  <p id="task-count"></p>
</div>

JavaScript — Full App

JavaScript
// State
let tasks  = JSON.parse(localStorage.getItem("tasks")) || [];
let filter = "all";

// Save to localStorage
function save() {
  localStorage.setItem("tasks", JSON.stringify(tasks));
}

// Add task
function addTask(text) {
  tasks.push({ id: Date.now(), text, done: false });
  save();
  render();
}

// Toggle done
function toggleTask(id) {
  const task = tasks.find(t => t.id === id);
  if (task) task.done = !task.done;
  save();
  render();
}

// Delete task
function deleteTask(id) {
  tasks = tasks.filter(t => t.id !== id);
  save();
  render();
}

Render Function

JavaScript
function render() {
  const list  = document.querySelector("#todo-list");
  const count = document.querySelector("#task-count");

  const filtered = tasks.filter(t => {
    if (filter === "active") return !t.done;
    if (filter === "done")   return t.done;
    return true;
  });

  list.innerHTML = filtered
    .map(task => `
      <li class="${task.done ? 'done' : ''}" data-id="${task.id}">
        <input type="checkbox" ${task.done ? 'checked' : ''}>
        <span>${task.text}</span>
        <button class="del-btn">×</button>
      </li>
    `)
    .join("");

  const remaining = tasks.filter(t => !t.done).length;
  count.textContent = `${remaining} task${remaining !== 1 ? 's' : ''} remaining`;
}

Event Listeners — Event Delegation

JavaScript
// Form submit
document.querySelector("#todo-form").addEventListener("submit", e => {
  e.preventDefault();
  const input = document.querySelector("#todo-input");
  if (input.value.trim()) {
    addTask(input.value.trim());
    input.value = "";
  }
});

// List clicks — delegation (one listener handles all items)
document.querySelector("#todo-list").addEventListener("click", e => {
  const li = e.target.closest("li");
  if (!li) return;
  const id = Number(li.dataset.id);

  if (e.target.type === "checkbox") toggleTask(id);
  if (e.target.matches(".del-btn")) deleteTask(id);
});

// Filter buttons
document.querySelector(".filters").addEventListener("click", e => {
  if (!e.target.matches(".filter-btn")) return;
  document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
  e.target.classList.add("active");
  filter = e.target.dataset.filter;
  render();
});

// Initial render
render();