Skip to Content
React in a Week
Day 3 · Interactivity

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

LessonTimeWhat you'll do
Responding to events45 minAttach click and change handlers, pass handlers as props, stop bubbling and form reloads
State: a component's memory45 minUse useState to remember values and re-render
Render, commit and state snapshots60 minLearn what a render is, why state is a snapshot, and how updater functions queue updates
Updating objects and arrays60 minAdd, remove and change items without mutation
Forms and controlled inputs60 minBuild controlled inputs and handle submits
Project checkpoint (below)60 minMake 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

Project checkpoint · Day 3 · Reading List

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.

App.js
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.

App.js
function handleDelete(id) {
  setBooks(books.filter((book) => book.id !== id));
}
BookCard.js
<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.

App.js
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.

App.js
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, splice or book.status = ... anywhere.
  • An empty or whitespace-only title can't be added.
  • Each BookCard still has key={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.

Live code· Reading List, end of Day 3

Tomorrow you'll add a filter bar, move these handlers into a reducer, and make the list survive a page refresh.

Day 3 progress

0/6
Next: Responding to events

On this page