Chapter 2: Build Core Todo
Completing Todos
Completing Todos
A todo app that can't mark things as done isn't much of a todo app. Let's add toggle functionality with checkboxes and visual feedback.
The prompt
"Update the TodoList component to add a checkbox before each todo item. When clicked, it should call an onToggle callback with the todo's ID. Completed todos should have a line-through style and lighter text. Add a smooth transition for the visual change."
What Claude Code updates
Claude Code will modify src/components/TodoList.tsx:
'use client';
import { Todo } from '@/types/todo';
interface TodoListProps {
todos: Todo[];
onToggle: (id: string) => void;
}
export default function TodoList({ todos, onToggle }: TodoListProps) {
return (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 divide-y divide-gray-100">
{todos.map((todo) => (
<div
key={todo.id}
className="p-4 flex items-center gap-3 group"
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
className="w-5 h-5 rounded border-gray-300
text-blue-500 focus:ring-blue-500
cursor-pointer"
/>
<div className="flex-1">
<p
className={`transition-all duration-200 ${
todo.completed
? 'line-through text-gray-400'
: 'text-gray-900'
}`}
>
{todo.title}
</p>
</div>
</div>
))}
</div>
);
}Add the toggle handler in the page
"Update page.tsx to pass an onToggle handler to TodoList that flips the completed status of the todo with the given ID."
Claude Code will add this to page.tsx:
const toggleTodo = (id: string) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// In the JSX:
<TodoList todos={todos} onToggle={toggleTodo} />The map approach creates a new array with the updated todo, which is how React expects you to update state. Never mutate state directly -- always create new objects. Claude Code follows this pattern automatically.
Understanding immutable updates
If the map pattern is new to you, ask Claude Code:
"Explain the map pattern for updating a single item in an array. Why can't I just do todos[index].completed = true?"
- React uses reference equality to detect changes. If you mutate an existing object, React doesn't know anything changed.
- The
mapfunction creates a new array with a new object for the changed todo. - React sees the new reference and re-renders the component.
- The spread operator
{ ...todo, completed: !todo.completed }copies all existing fields and overrides just the one we want to change.
Test it
- Click the checkbox next to a todo
- The text should get a line-through and turn gray
- Click again to uncheck -- it should go back to normal
- The transition should be smooth, not instant
Try checking all items, then unchecking one. Notice that each todo's state is independent. This is because we're updating by ID, not by index.
A satisfying detail
The group class on the parent div and the transition-all on the text are small CSS touches, but they make the interaction feel polished. Claude Code adds these details when you mention "smooth transition" in your prompt.
Next up: deleting todos -- the "D" in CRUD.