Reactadvanced 12 min
A complete task tracker that combines components, props, state, lists, forms, derived data and cleanup.
How to combine everything from this track into a small but complete app: a task tracker with adding, completing, deleting, filtering and counting tasks.
Knowing each idea separately is only half the skill. Building something whole shows how components, props, state and lists fit together, and gives you a pattern you can reuse for almost any small app.
The plan for a good React app:
App component holds the list of tasks and the current filter.tasks and filter on every render, so they can never get out of sync.localStorage in a useEffect.App owns the two pieces of real state: tasks and filter (and a counter for ids).AddTask keeps its own small piece of state, the text being typed, because nothing else needs it. When submitted it calls the onAdd function from its parent.TaskItem is a pure display component: it receives a task plus two callbacks and has no state.toggle uses map to produce a new array where only the matching task is replaced by a copy with done flipped.remove uses filter to drop one task.visible and remaining are derived from the state on each render. The three filter buttons update filter, and the list updates automatically.key={t.id}.Add a task, tick a few, switch filters and delete one. Then add a "Clear completed" button that keeps only the unfinished tasks.
remaining), which then gets out of sync.task.done = true) instead of copying.You finished the React track. Try the practice exercises, then build your own app from scratch.
function AddTask({ onAdd }) {
const [text, setText] = useState("");
function submit(e) {
e.preventDefault();
if (text.trim() === "") return;
onAdd(text.trim());
setText("");
}
return (
<form onSubmit={submit}>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="New task" />
<button type="submit">Add</button>
</form>
);
}
function TaskItem({ task, onToggle, onDelete }) {
return (
<li>
<label style={{ textDecoration: task.done ? "line-through" : "none" }}>
<input type="checkbox" checked={task.done} onChange={() => onToggle(task.id)} /> {task.text}
</label>
<button onClick={() => onDelete(task.id)} style={{ marginLeft: 8 }}>x</button>
</li>
);
}
function App() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Learn React", done: true },
{ id: 2, text: "Build a project", done: false },
]);
const [filter, setFilter] = useState("all");
const [nextId, setNextId] = useState(3);
function add(text) {
setTasks([...tasks, { id: nextId, text, done: false }]);
setNextId(nextId + 1);
}
const toggle = (id) => setTasks(tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
const remove = (id) => setTasks(tasks.filter((t) => t.id !== id));
const visible = tasks.filter((t) =>
filter === "all" ? true : filter === "done" ? t.done : !t.done
);
const remaining = tasks.filter((t) => !t.done).length;
return (
<div style={{ fontFamily: "system-ui" }}>
<h2>Task tracker</h2>
<AddTask onAdd={add} />
<p>
{["all", "active", "done"].map((f) => (
<button key={f} onClick={() => setFilter(f)} disabled={filter === f} style={{ marginRight: 6 }}>
{f}
</button>
))}
</p>
<ul>
{visible.map((t) => (
<TaskItem key={t.id} task={t} onToggle={toggle} onDelete={remove} />
))}
</ul>
<p>{remaining} of {tasks.length} tasks left</p>
</div>
);
}