Stop 22 · Build Portfolio ProjectsProjects · 2/15

Todo App

Part of the Frontend Developer Roadmap

In short

A todo app is the 'hello world' of React. It earns a spot in your portfolio only when it goes further: saved data, filters, editing, and zero bugs when you refresh.

A production-grade todo app with persistence, categories/tags, due dates and possibly drag-and-drop reordering — showing you can extend a simple idea thoughtfully.

The jaadu ticks off a grocery list stuck to the fridge, a bag of vegetables nearby.

Think of it like this

Mummy's monthly grocery list

Items get added all month, some get struck off, some get edited ('2 kg, not 1'). The list sits on the fridge, so nothing is lost overnight. Your todo app should work just like that list.

Watch it happen

How todos survive a refresh

You
React state
localStorage

add 'Buy milk'

1/6You add a todo. setTodos creates a new array with the item at the end.

The big ideas

What you need to know

Survives a refresh

Save to localStorage so todos are still there tomorrow. This alone sets you apart from most beginner versions.

Full CRUD

Add, edit in place, tick off and delete. Proves you can handle all four operations cleanly.

Filters and due dates

All, active, done, plus due dates. Shows you can derive views from one list of data.

Keyboard friendly

Enter to add, real checkboxes, visible focus. Small things recruiters do notice.

How it works

Build plan

  1. 1

    Model the data

    Each todo has id, text, done and dueDate.

  2. 2

    Add and list

    A form that adds to state and a list that renders it.

  3. 3

    Edit, toggle, delete

    Update state immutably for each action.

  4. 4

    Persist

    Save to localStorage on change, load it on first render.

  5. 5

    Filters and polish

    Add filters, empty states and a deployed link.

See it in code

Walk through the code

jsx
1const [todos, setTodos] = useState(() => {2  const saved = localStorage.getItem("todos");3  return saved ? JSON.parse(saved) : [];4});5 6useEffect(() => {7  localStorage.setItem("todos", JSON.stringify(todos));8}, [todos]);9 10const toggle = (id) =>11  setTodos((prev) =>12    prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))13  );

1/3The function form of useState reads storage only on the first render.

The function passed to useState reads storage only once. In Next.js, put this in a client component and read storage in useEffect instead, since the server has no localStorage.

Save yourself the pain

Common mistakes

Oops

Doing todos.push(newTodo) and then setTodos(todos).

Do this instead

Make a new array: setTodos([...todos, newTodo]). React compares references, so a mutated array may not re-render.

Oops

Using the array index as the React key.

Do this instead

Give each todo a stable id, like crypto.randomUUID(). Index keys break when you delete or reorder.

Your mission

Milestone 1: todos that remember

~40 min

Quick check

You write todos.push(item); setTodos(todos); and the list doesn't update. Why?

Go deeper

  1. 1Add categories/tags and due dates, not just a flat task list.
  2. 2Implement drag-and-drop reordering with a library like dnd-kit.
  3. 3Add a real backend (or Firebase) instead of only localStorage, for persistence across devices.

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