Chapters

Hide chapters

React Apprentice

First Edition · web · React 8.0.0 · Visual Studio Code

Section I: Rendering Right

Section 1: 7 chapters
Show chapters Hide chapters

8. Events & Local State
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapter, you completed Part I: a typed, styled, accessible course catalog with a folder structure worth showing off. It has exactly one shortcoming — it’s a statue. The “Add to favorites” buttons have been printing console messages since Chapter 4, politely waiting for this moment.

This chapter makes the app react. Two ideas carry the whole of Part II, and you’ll meet both today. Events let the user talk to your components — clicks, presses, typing. State is what lets a component remember the answer and change what it shows. Wire them together and you get the loop every interactive interface runs on: The user acts, state changes, React re-renders, the screen answers.

By the end of the chapter, every course card will own a working favorite toggle — visuals, message and accessibility semantics all driven by state — and you’ll understand the single most important rule of React state: What your component sees is a snapshot, not a live wire.

Renaming the Placeholder

Start from what’s already there. In src/components/CourseCard.tsx, the card has carried this since Chapter 4:

function noteFavoriteClick() {
  console.log('Favorites arrive in Chapter 8!')
}

The wiring already works — Button hands the function to React’s onClick prop on a real DOM button, and React calls it whenever the browser reports a click. That function is an event handler, and the only thing wrong with it is the name and the job. Rename it to match the convention you’ll see across the React world — handle plus the event it answers:

function handleFavoriteClick() {
  console.log('Favorite clicked!')
}

And update the reference in the JSX below:

onClick={handleFavoriteClick}

Save, check the browser, and click a button with the console open — the new message appears. No visible page change, but the loop’s first half — click to handler — is confirmed working.

Note: A reminder from Chapter 4 that becomes critical now: onClick={handleFavoriteClick} passes the function itself, as a value, for React to call when the click arrives. onClick={handleFavoriteClick()} calls it immediately — during render — and passes its result. Get this wrong with a state-updating handler, and you’ll trigger an update during every render: an infinite loop with a helpful error. When in doubt: no parentheses.

Trying the Obvious Thing

The handler should flip whether this course is a favorite. The obvious approach: Keep a variable, change it on click. Try it, so you can watch it fail. In CourseCard, add a variable above the handler and use it:

let isFavorite = course.isFavorite

function handleFavoriteClick() {
  isFavorite = !isFavorite
  console.log('isFavorite is now', isFavorite)
}
{isFavorite && (

State: The Memory That Triggers Renders

React’s answer is state — the concept Chapter 2 waved at from a distance, finally in your hands. State is data that belongs to a component instance, survives across renders and, when changed through React, schedules a re-render. You create it with useState, the first of React’s hooks — functions starting with use that give components superpowers. They’re callable only at the top level of a component — or of another hook, a possibility Chapter 13 unlocks.

The interaction loop: click, handler, state update, render, commit — then ready for the next click.
Zne ajfixihbaot ceuc: rreww, buxmqep, cmupi iqzoci, wahjeg, jezkuy — bhiy joezc dil zye fexb bvivj.

Making the Favorite Toggle Work

In src/components/CourseCard.tsx, add the import at the top of the file:

import { useState } from 'react'
const [isFavorite, setIsFavorite] = useState(course.isFavorite)
function handleFavoriteClick() {
  setIsFavorite(!isFavorite)
}
<Button
  label="Favorite"
  onClick={handleFavoriteClick}
  pressed={isFavorite}
/>
type ButtonProps = {
  label: string
  onClick: () => void
  variant?: 'primary' | 'ghost'
  pressed?: boolean
}
aria-pressed={pressed}
.button[aria-pressed='true'] {
  background: #16294d;
  border-color: #16294d;
}

.button[aria-pressed='true']::before {
  content: '\2605  ';
}
Both looks, straight from the data: two cards start favorited, two don't.
Hufn vaosz, ckzuibqg kjev gmi xabi: zqa setfk xkucw fajukexom, dwi gas't.

One click later: the star and note are gone, and the card re-rendered itself.
Umo twamf quweh: xki yrep ilz zeku egi tuqe, ump bzu kumc lu-xasjolur entevv.

Each Card Remembers for Itself

Toggle a few cards into different combinations. Notice that every card keeps its own answer — favoriting CSS Layout doesn’t touch Modern TypeScript. Each rendered <CourseCard /> is a separate instance, and useState gives each instance its own private memory. Same component function, four independent favorites.

The Snapshot Rule

One experiment left, and it prevents the most common React bug there is. In handleFavoriteClick, log the state right after setting it:

function handleFavoriteClick() {
  setIsFavorite(!isFavorite)
  console.log('after the setter:', isFavorite)
}
after the setter: false

Challenge: Add a Details Toggle

Give each card a second piece of state: a details toggle. A ghost button at the bottom of the card — labeled Hide details or Show details depending on state — that shows or hides the description, the duration line and the meter, with details visible by default.

Key Points

  • An event handler is a function you pass to props like onClick — passed without parentheses, called by React when the event fires.
  • Plain variables can’t power interactivity: They neither survive re-renders nor cause them.
  • useState(initialValue) returns the current value and a setter via array destructuring; TypeScript infers the state type from the initial value.
  • Calling the setter stores the value for the next render and schedules that render — the loop is click → handler → state update → render → commit, with commit being Chapter 2’s apply-to-the-page phase.
  • Derive what you can from state — like favoriteLabel — instead of storing extra copies.
  • Each component instance owns its state independently; local state is invisible to parents and siblings.
  • A toggle button keeps a stable label and announces its state through aria-pressed — and CSS can style straight off that attribute, keeping pixels and semantics in sync.
  • State is a snapshot: After a setter call, the state variable still holds the current render’s value — the new value arrives with the next render.

Where to Go From Here?

The Learning Tracker finally responds to its user — four independent toggles, each running the full event-to-commit loop. You also stubbed your toe, on purpose, against local state’s ceiling: cards that can’t share what they know. Hold that thought for Chapter 11.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now