Project 1

Project 1
Todo List
The classic first project — add, complete, and remove items from a list, all driven by state
Difficulty: Beginner Builds on: Fundamentals Ch 1–7

Unlike the chapter challenges, this is a project brief, not a graded exercise — there's no single correct solution file. Instead, you get requirements, a suggested structure, and stretch goals; how you actually build it is up to you. A todo list might seem simple, but it touches almost every Fundamentals concept at once: state, lists, conditional rendering, controlled inputs, and component composition, all working together in one small app.

Skills This Project Exercises
useState Controlled inputs .map() + key Conditional rendering Lifting state up Props & callbacks

Requirements

  • A text input and "Add" button for entering a new todo — pressing Enter should also work, not just clicking the button.
  • Each todo displays its text and has a way to mark it complete (e.g. a checkbox) and a way to delete it.
  • Completed todos should look visually distinct from incomplete ones (e.g. strikethrough text, dimmed color).
  • The input should clear itself after a todo is successfully added.
  • Adding an empty/blank todo should be prevented.
  • If the list is empty, show a friendly message (e.g. "No todos yet — add one above!") instead of an empty list.

Suggested Component Breakdown

App └── TodoApp // holds the array of todos in state ├── TodoForm // controlled input + add button └── TodoList // maps over todos, renders one TodoItem per todo └── TodoItem (×N) // checkbox, text, delete button — one per todo

This isn't the only valid structure — it's a starting point. The key decision worth sticking to: the array of todos itself should live in TodoApp (the lowest common ancestor of the form and the list), with TodoForm and TodoItem each receiving only the props and callbacks they specifically need, exactly as covered in Fundamentals Chapter 10.

A Reasonable Build Order

  1. Get a static version working first — hardcode a small array of 2–3 todo objects ({ id, text, completed }) and render them with TodoList/TodoItem before wiring up any state changes.
  2. Move that array into useState in TodoApp, so it's now real state instead of a hardcoded constant.
  3. Wire up TodoForm to add a new todo object to the array on submit, generating a unique id for each one (e.g. Date.now() is a quick option for a small project like this).
  4. Add the "toggle complete" behavior — clicking a todo's checkbox should flip its completed value without affecting any other todo in the array.
  5. Add the delete behavior — removing exactly one todo from the array by its id, leaving the rest unchanged.
  6. Add the empty-state message and the blank-input guard last, once the core add/toggle/delete flow is solid.
Stretch Goals
  • Add an "edit" mode — double-clicking a todo's text turns it into an editable input.
  • Add filter buttons (All / Active / Completed) above the list.
  • Show a live count of remaining (incomplete) todos.
  • Persist the list to localStorage so it survives a page refresh (a preview of Intermediate Chapter 6's data-handling patterns).
  • Add a "Clear completed" button that removes every completed todo at once.
This project has no single solution file — the requirements above and your own judgment are the spec. Compare notes against the suggested component breakdown once you have something working, not before.