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.

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
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
Model the data
Each todo has id, text, done and dueDate.
- 2
Add and list
A form that adds to state and a list that renders it.
- 3
Edit, toggle, delete
Update state immutably for each action.
- 4
Persist
Save to localStorage on change, load it on first render.
- 5
Filters and polish
Add filters, empty states and a deployed link.
See it in code
Walk through the code
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
Quick check
You write todos.push(item); setTodos(todos); and the list doesn't update. Why?
Go deeper
- 1Add categories/tags and due dates, not just a flat task list.
- 2Implement drag-and-drop reordering with a library like dnd-kit.
- 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.