Stop 04 · JavaScriptProjects · 1/8

Todo

Part of the Frontend Developer Roadmap

In short

The Todo app is the "hello world" of real projects, and there's a reason. It makes you join state, the DOM, events and storage into one thing that actually works.

The classic first JS project — add, complete, delete and filter tasks — practicing DOM manipulation, events, arrays and localStorage persistence together. This project combines state, events, DOM updates, persistence, filtering, and user interaction, making it a strong exercise for connecting multiple beginner JavaScript concepts.

The jaadu mascot sticks colourful sticky notes on a hostel wall and ticks one off.

Think of it like this

Sticky notes before exams

Before exams, your hostel wall fills with sticky notes. You add one, strike it off when done, peel off the ones you don't need, and they're still there the next morning. Your Todo app is that wall, minus the glue marks.

Watch it happen

How the Todo app's parts talk

User
todos array
render()
localStorage

add "Buy milk"

1/6The user submits a new task. It's pushed into the todos array first.

The big ideas

What you need to know

State is the truth

Keep all todos in one array of objects. The page is just a picture of that array.

One render function

Change the array, then call render() to redraw the list. No hand-editing the DOM in five places.

Delegate clicks

One listener on the list handles tick and delete for every item, including ones added later.

Survive a refresh

Save the array to localStorage after every change and load it when the page opens.

How it works

Build plan

  1. 1

    Skeleton

    An input, an Add button and an empty <ul> in HTML.

  2. 2

    State and render

    A todos array and a render() that draws it.

  3. 3

    Add, tick, delete

    Update the array on each action, then re-render.

  4. 4

    Persist

    Save to localStorage on every change and load on startup.

  5. 5

    Filters and polish

    All, Active and Done tabs, an item count and empty-state text.

See it in code

Walk through the code

js
1let todos = JSON.parse(localStorage.getItem("todos") ?? "[]");2 3function save() {4  localStorage.setItem("todos", JSON.stringify(todos));5}6 7function render() {8  const list = document.querySelector("#list");9  list.replaceChildren(...todos.map((t) => {10    const li = document.createElement("li");11    li.textContent = t.text;12    li.dataset.id = t.id;13    li.classList.toggle("done", t.done);14    return li;15  }));16}

1/4Load saved todos, or start with an empty array the first time.

replaceChildren clears the list and adds fresh items in one go. Every add, tick or delete should change todos, call save(), then call render().

Save yourself the pain

Common mistakes

Oops

Editing the DOM directly and forgetting to update the array.

Do this instead

The page and your data drift apart, and the next refresh brings back deleted items. Always change state first, then render.

Oops

Using the array index as a todo's id.

Do this instead

Indexes shift when you delete. Give each todo a real id, like crypto.randomUUID() or Date.now().

Oops

Setting li.innerHTML = todo.text.

Do this instead

A todo like <img onerror=...> would run code. Use textContent for anything the user typed.

Your mission

Milestone 1: add and remember

~40 min

Quick check

You write localStorage.setItem("todos", todos) where todos is an array of objects, without JSON.stringify. What gets saved?

Go deeper

  1. 1Persist tasks to localStorage so they survive a page refresh. Validate your understanding by applying it in a small practical example.
  2. 2Add filtering (all/active/completed) using array `.filter()`. Test the result on both a successful path and an invalid or unexpected input.
  3. 3Practice event delegation for delete buttons on dynamically added items.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge