Add, toggle, delete
All three features are immutable array updates from lesson 4-3, wrapped in handlers. We write them as pure functions that take the current tasks and return a new array, so we can run and test them right here without React:
| Feature | Tool | Shape |
|---|---|---|
| add | spread | [...tasks, newTask] |
| toggle | map | replace one item with a changed copy |
| delete | filter | keep everything except one id |
Inside TaskApp, each handler feeds its helper into the state setter using the functional-update form from lesson 4-2:
function handleAdd(e) { e.preventDefault(); // lesson 6-2 if (text.trim() === "") return; setTasks(ts => addTask(ts, nextId(), text.trim())); setText(""); // clear the controlled input } function handleToggle(id) { setTasks(ts => toggleTask(ts, id)); } function handleDelete(id) { setTasks(ts => deleteTask(ts, id)); }
Code exercise · javascript
Your turn, the heart of the app. addTask is done for you. Complete toggleTask (flip done on the matching id using map and spread, leave others untouched) and deleteTask (filter the id out). The log lines must match exactly.
Wiring the children
The leaf components are small. TaskItem renders one task and reports events upward:
function TaskItem({ task, onToggle, onDelete }) { return ( <li> <input type="checkbox" checked={task.done} onChange={() => onToggle(task.id)} /> {task.done ? <s>{task.title}</s> : task.title} <button onClick={() => onDelete(task.id)}>✕</button> </li> ); } function TaskList({ tasks, onToggle, onDelete }) { return ( <ul> {tasks.map(task => ( <TaskItem key={task.id} task={task} onToggle={onToggle} onDelete={onDelete} /> ))} </ul> ); }
Every course concept is on display: props and destructuring (3-1), a controlled checkbox (6-1), a ternary render (5-3), map with a stable key={task.id} (5-1, 5-2), and function props carrying changes up (8-1).
Quiz
In TaskItem, why is the handler written onChange={() => onToggle(task.id)} instead of onChange={onToggle(task.id)}?