Professional React · Lesson 21 of 21
Project: a complete to-do app
Add, delete, complete, filter and count, step by step.
We'll build a study to-do app in three stages, each a self-checking exercise. By the end you'll have: adding tasks, marking them done, deleting them, filtering (all / active / done), and a counter.
Here's the finished version, try it:
function App() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Watch the useState lesson", done: true },
{ id: 2, text: "Solve 3 exercises", done: false },
])
const [text, setText] = useState("")
const [filter, setFilter] = useState("all")
function add(e) {
e.preventDefault()
if (!text.trim()) return
setTasks([...tasks, { id: Date.now(), text: text.trim(), done: false }])
setText("")
}
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 left = tasks.filter((t) => !t.done).length
return (
<div style={{ maxWidth: 360 }}>
<form onSubmit={add}>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="New task" />
<button>Add</button>
</form>
<ul style={{ paddingInlineStart: 18 }}>
{visible.map((t) => (
<li key={t.id}>
<label style={{ textDecoration: t.done ? "line-through" : "none" }}>
<input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} /> {t.text}
</label>{" "}
<button onClick={() => remove(t.id)}>✕</button>
</li>
))}
</ul>
<p>{left} left · {["all", "active", "done"].map((f) => (
<button key={f} onClick={() => setFilter(f)} disabled={filter === f}>{f}</button>
))}</p>
</div>
)
}Only three pieces of state; the rest is computed.
Stage 1: adding
Exercise
Finish the form: on submit, add the task to the list and clear the input; never add an empty task. The checker types Read and presses Add.
Add Read 1 tasks
function App() {
const [tasks, setTasks] = useState([])
const [text, setText] = useState("")
function add(e) {
e.preventDefault()
// add the task here
}
return (
<form onSubmit={add}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button>Add</button>
<ul>{tasks.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
<p>{tasks.length} tasks</p>
</form>
)
}Stage 2: completing and counting
Exercise
Each task has a checkbox that flips done, and the last line shows N left (tasks not done). The checker ticks the first task.
Read Practice Quiz 1 left
function App() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Read", done: false },
{ id: 2, text: "Practice", done: false },
{ id: 3, text: "Quiz", done: true },
])
return (
<div>
<ul>
{tasks.map((t) => (
<li key={t.id}><input type="checkbox" checked={t.done} readOnly /> {t.text}</li>
))}
</ul>
<p>? left</p>
</div>
)
}Stage 3: filtering
Exercise
Add three filter buttons: all, active and done. The list shows tasks for the chosen filter. The checker presses done, so only Quiz should show.
all active done Quiz
const TASKS = [
{ id: 1, text: "Read", done: false },
{ id: 2, text: "Practice", done: false },
{ id: 3, text: "Quiz", done: true },
]
function App() {
const [filter, setFilter] = useState("all")
const visible = TASKS
return (
<div>
{["all", "active", "done"].map((f) => (
<button key={f} className={f}>{f}</button>
))}
<ul>{visible.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
</div>
)
}Learn more from
Played 0 of 3 games
Sign in to save your progress and earn XP.