Day 3: Interactivity
Make your components respond to people with events, state, immutable updates and forms.
Yesterday you learned to describe a UI: components, props, lists and conditional rendering. Everything you built was static. It showed data, but nothing happened when you clicked.
Today your app comes alive. You'll learn how React responds to events, how components remember things with state, what really happens when state changes, and how to update lists and objects safely. These ideas are the core of React. Almost every feature you build from here on is "an event changes some state, and the UI re-renders to match."
Today's plan
| Lesson | Time | What you'll do |
|---|---|---|
| Responding to events | 45 min | Attach click and change handlers, pass handlers as props, stop bubbling and form reloads |
| State: a component's memory | 45 min | Use useState to remember values and re-render |
| Render, commit and state snapshots | 60 min | Learn what a render is, why state is a snapshot, and how updater functions queue updates |
| Updating objects and arrays | 60 min | Add, remove and change items without mutation |
| Forms and controlled inputs | 60 min | Build controlled inputs and handle submits |
| Project checkpoint (below) | 60 min | Make the Reading List interactive |
That's about five and a half hours. If you're short on time, do the lessons today and the checkpoint tomorrow morning. Day 4 builds directly on it.
What you'll build today
By the end of the day, your Reading List stops being a static page. You'll be able to:
- add a book with a small form (title and author);
- cycle a book's status with one click: want to read, then reading, then done;
- delete a book.
All of it uses state in App and immutable updates, which sets you up for Day 4, when you'll add filtering, a reducer and saving to local storage.
Lessons
Responding to events
Event handlers, handler props, the event object, propagation and preventDefault.
State: a component's memory
Why variables aren't enough, useState, multiple state variables and the rules of Hooks.
Render, commit and state snapshots
Trigger, render, commit; state as a snapshot; batching and updater functions.
Updating objects and arrays
Spread, map and filter: changing state without mutation.
Forms and controlled inputs
Controlled inputs, checkboxes and selects, submitting and lifting data up.
Project: an interactive Reading List
At the end of Day 2, your app had a data.js file exporting initialBooks, a BookList that maps books to BookCard components, and a status badge. Today you'll move the books into state and add three features. Work in your local reading-list project, or follow along in the sandbox at the end.
Starting point
Today's final code also tidies up the markup a little: BookList becomes a <ul> with one <li> per book, and BookCard gets an actions area for its new buttons, with matching CSS. If your Day 2 files look different, that's expected. When you reach the end, copy the complete BookList.js, BookCard.js and styles.css from the final sandbox into your project, so that your code matches the rest of the course.
Put the books in state
Right now BookList reads initialBooks directly, so the list can never change. Move it into state in App, the component that owns the data, and pass the books down as a prop.
import { useState } from 'react';
import { initialBooks } from './data.js';
import { BookList } from './BookList.js';
export default function App() {
const [books, setBooks] = useState(initialBooks);
return (
<main>
<h1>My Reading List</h1>
<BookList books={books} />
</main>
);
}initialBooks is now only the starting value. From here on, books is the source of truth.
Delete a book
Write a handler in App that removes a book by its id using filter. Then pass it down: App gives it to BookList as onDelete, and BookList gives it to each BookCard.
function handleDelete(id) {
setBooks(books.filter((book) => book.id !== id));
}<button className="danger" onClick={() => onDelete(book.id)}>
Delete
</button>The card doesn't know how deleting works. It only says "delete this id," and App decides what that means. That's the handler-props pattern from the events lesson.
Cycle the status
Add a pure helper that returns the next status. After 'done' it wraps back to 'want', so you can always undo an accidental click. Then write handleStatusChange with map and spread.
function nextStatus(status) {
if (status === 'want') return 'reading';
if (status === 'reading') return 'done';
return 'want';
}
// inside App:
function handleStatusChange(id) {
setBooks(
books.map((book) =>
book.id === id ? { ...book, status: nextStatus(book.status) } : book
)
);
}Pass it down as onStatusChange and attach it to a button in BookCard. Make the button's label tell the user what will happen next, like "Start reading".
Add a book with a form
Create AddBookForm.js with two controlled inputs, title and author. On submit, call onAdd(title, author) and clear the inputs. In App, create the new book with an ID from crypto.randomUUID() and add it with spread.
function handleAddBook(title, author) {
const newBook = {
id: crypto.randomUUID(),
title,
author,
status: 'want',
};
setBooks([...books, newBook]);
}Then render <AddBookForm onAdd={handleAddBook} /> above the list.
Check your work
Try each feature: add a book, cycle its status all the way round, delete it. Also check:
- Every update creates a new array or object. There's no
push,spliceorbook.status = ...anywhere. - An empty or whitespace-only title can't be added.
- Each
BookCardstill haskey={book.id}, and new books get a unique ID.
The full code for Day 3
Here's the complete app. If your Day 2 data had different books, that's fine; only the shape matters: id, title, author, status and an optional rating.
Tomorrow you'll add a filter bar, move these handlers into a reducer, and make the list survive a page refresh.
Day 3 progress