Todo List Walkthrough — Overview

Todo List Walkthrough — Overview

A multi-step code challenge to learn how useState works, how parent and child components share data, and how event handlers flow up while data flows down.

You’ll build a small Todo app from scratch. Each step adds one feature. Each step page has a prompt, hints, the React concepts in play, and a solution at the bottom you can scroll to if stuck.

What you’ll build

A TodoList with three components:

  • TodoList — owns the array of todos and the handlers that mutate it.
  • TodoComposer — a controlled <input> + button for adding new todos.
  • Todo — renders a single todo with checkbox, edit/save toggle, and delete.

By the end, you’ll be able to add, toggle, edit, and delete todos.

What you’ll learn

  • How useState actually behaves across renders.
  • The “data down, handlers up” pattern — children never reach into the parent’s state.
  • Immutable updates with { ...todo } and arr.map / arr.filter.
  • How to decide whether state belongs in the parent or the child.
  • How conditional rendering ({cond ? A : B}, {cond && A}) interacts with state.

Prerequisites — read these first

If any of these are unfamiliar, read them before starting. Each is short.

How to work through this

  1. Set up a scratch React project (Vite + React, your existing one, anything that runs JSX).
  2. Read each step page top-to-bottom before looking at its Solution section.
  3. Try to write the code yourself. Run it. See what breaks.
  4. Only then scroll to Solution and compare.
  5. Read Why this works even if your solution matched — it explains the idea, not just the answer.

Source attribution

The “final code” this walkthrough builds toward came from a course exercise. It’s been lightly cleaned up:

  • Imports use import { useState } from 'react' (named imports, matching the rest of this wiki).
  • A dead useState in the original Todo component has been removed (mentioned again in step 9).
  • Random integer IDs have been replaced with crypto.randomUUID() (mentioned again in step 6).

The full final code is on Recap.

Steps

  1. Setup — three component files, what each owns.
  2. Rendering a static list.map(), key, no state yet.
  3. Holding the list in state — convert array to useState.
  4. Extracting <Todo /> — props, single-responsibility.
  5. Adding todos<TodoComposer />, lifting state up.
  6. Toggling completed — object spread, immutable update.
  7. Deleting a todo.filter() pattern.
  8. Editing a todo — local component state, conditional rendering.
  9. Recap — full final code, lessons learned.