Without notes, state yesterday’s main idea and one unresolved question.
State and event handling
React Frontend Fundamentals
Objective
Model interactive values and update them through events.
A React component is a reusable functional block with inputs (props), internal state, and rendered output.
- useState
- event handlers
- immutable updates
Why this matters
So far your dashboard is a photograph: data goes in at the top, pixels come out, nothing moves. Today it becomes an instrument. State is the memory a component keeps between renders, and changing it is the only thing that makes React redraw the screen.
By the end of the hour your equipment list will respond to a search box, a status filter, and a row selection — and the original array it was built from will be byte-for-byte unchanged.
What a render actually is
Say it precisely, because everything today depends on it. A render is React calling your component function and keeping the elements it returns. React renders a component when:
- it is rendered for the first time, or
- its state is set to a new value, or
- its parent re-renders.
That list is complete. Nothing else triggers it — not assigning to a variable, not mutating an
array, not changing something in localStorage.
Here is the trap that follows. A component function runs from the top every render, so every plain variable inside it is created fresh and thrown away:
function SearchBox() {
let query = ""; // reset on every render
return <input value={query}
onChange={(event) => { query = event.target.value; }} />; // nothing happens
}
The assignment works — the variable really does change. But React was never told to re-render, and
even if something else caused one, query would be reset to "" on the way in. The box stays
empty, and that is your first genuinely confusing React bug.
useState: memory that survives a render
useState is a hook — a function starting with use that lets a component tap into React
features. It gives a component a value that persists across renders, plus the only sanctioned way
to change it.
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
);
}
useState("") returns an array of exactly two things, which you destructure: the current value and
a function that sets it. "" is the initial value, used on the first render only. The
[thing, setThing] naming convention is universal — follow it.
Calling setQuery("pu") does two things: it stores "pu" as this component's state, and it tells
React to render this component again. On that next render, useState("") returns "pu", and the
input shows it. That loop — set state, re-render, read the new value — is the engine of every
React app.
Two rules the tooling enforces:
- Hooks only run at the top level of a component function. Not inside
if, not inside a loop, not inside a nested function. React matches hooks to state by call order, so the order must be identical on every render. - State is per-instance. Render
<SearchBox />twice and each copy has its ownquery— same function, two independent memories.
Props are input pins; state is the latch inside
The block diagram gets one more part today. Props are the input pins, driven from outside and
read-only to the block. State is a latch inside the block that holds a value between updates.
setQuery is the write strobe: it loads the latch and re-evaluates the block's output. Writing
to an internal node without strobing — the broken query = ... above — leaves the output showing
the old value.
The state variable does not change inside the handler
function handleClick() {
setCount(count + 1);
console.log(count); // still the OLD number
}
count is a const belonging to this render. Setting state schedules the next render; it does
not rewrite the current one. When the new value depends on the old, pass a function instead:
setCount((current) => current + 1). React calls it with the latest value.
Event handlers
An event handler is a function you hand to React through a prop like onClick or onChange. React
attaches the listener for you — no addEventListener from Day 25.
function handleReset() {
setQuery("");
}
<button type="button" onClick={handleReset}>Clear</button>
Pass the function; do not call it. onClick={handleReset} gives React the function to call later.
onClick={handleReset()} calls it immediately during render and hands React whatever it returned —
usually undefined, and if it sets state you get an infinite render loop.
When you need to pass an argument, wrap it in an arrow function so the call is deferred:
<button type="button" onClick={() => setSelectedId(item.id)}>{item.name}</button>
The handler receives an event object. event.target.value is an input's current text;
event.preventDefault() stops the browser default, which you need tomorrow for forms.
Use a real <button type="button"> for anything clickable. A <div onClick=...> is not focusable,
ignores Enter and Space, and is announced as nothing by a screen reader — the
Week 2 rule has not gone away.
Immutable updates
When state holds an array or an object, you must replace it, never edit it in place. Two reasons, both concrete.
First, React decides whether state actually changed by comparing the new value with the old using
Object.is. items.push(newItem) hands back the same array reference, so React compares it with
itself, sees no change, and skips the render. Your data changed and your screen did not.
Second, rendering must be pure. If a component edits an array it received, the next render starts from data the previous render corrupted, and the result depends on how many times React happened to render. The Day 22 transformation methods are exactly the tools for this:
setItems([...items, newItem]); // add
setItems(items.filter((item) => item.id !== id)); // remove
setItems(items.map((item) => // change one
item.id === id ? { ...item, status: "down" } : item,
));
setFilters({ ...filters, status: "down" }); // object field
Each line produces a new array or object and leaves the old intact. { ...item, status: "down" }
copies every field of item, then overrides status.
Editing a signed document
You do not scribble a new figure onto a signed report and hand it back — everyone holding the original now disagrees with you. You photocopy it, change the line, and circulate the new version. React compares versions; edit in place and there is no new version to notice.
Do not store what you can derive
The most common React design mistake is putting a value in state that could be calculated from
other state. Keep query and filteredItems in state and they can disagree — and one day they
will, because someone updated one and not the other. Derive during render instead:
const [query, setQuery] = useState("");
const [status, setStatus] = useState<Equipment["status"] | "all">("all");
// derived — recomputed every render, never stored, never stale
const visible = items
.filter((item) => item.name.toLowerCase().includes(query.toLowerCase()))
.filter((item) => status === "all" || item.status === status);
Ask of every candidate: can I compute this from props or other state? If yes, compute it. State is for what the user changed and nothing else can tell you: text typed, a selected id, an open flag.
State also has to live somewhere specific: the closest common parent of every component that
needs it. If the search box sets query and the list reads it, query belongs to the parent that
renders both, and the box gets query and onQueryChange as props. That is lifting state up,
the standard fix for "two components need the same value".
Walkthrough
Create src/components/EquipmentBrowser.tsx. This component owns the state; everything below it
stays a plain function of its props.
import { useState } from "react";
import type { Equipment } from "../types";
import { EquipmentList } from "./EquipmentList";
export function EquipmentBrowser({ items }: { items: Equipment[] }) {
const [query, setQuery] = useState("");
const visible = items.filter((item) =>
item.name.toLowerCase().includes(query.toLowerCase()),
);
return (
<section>
<label htmlFor="equipment-search">Search equipment</label>
<input
id="equipment-search"
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
<p>{visible.length} of {items.length} shown</p>
<EquipmentList items={visible} />
</section>
);
}
Render it from App.tsx and type in the box. Each keystroke fires onChange, which calls
setQuery, which re-renders EquipmentBrowser, which recomputes visible and renders a shorter
list. items is never touched.
Checkpoint
Type three characters and say what happened three times, in order: event → set state → render →
new derived value → new DOM. Then confirm items still has all four entries by logging
items.length.
Pair mode — while you design the state
Today's mode is pair: the AI proposes, you verify. Never accept a change you have not read; inspect the diff, run it, and be able to explain the behaviour first.
"Help me identify the minimal state. Challenge any value that could be derived."
Push back when it suggests storing a filtered list. You now know why that is wrong.
Your turn
Deliverable: the list responds correctly without mutating source arrays.
- Add the search box from the walkthrough. Confirm the
<label>hashtmlFormatching the input'sid, then click the label text and check focus lands in the input. - Add a status filter with
useState<Equipment["status"] | "all">("all")and a<select>whosevalueandonChangeare wired like the search input. Give it a<label>too. - Chain both conditions into one derived
visiblearray. Do not store it in state. - Add
const [selectedId, setSelectedId] = useState<string | null>(null). Make each row's name a<button type="button">that sets it, and show details for the selected item below the list. - Add a "Clear filters" button that resets all three pieces of state. Check the list returns to four items.
- Add a "Mark down" button on the selected item that calls
setItems(items.map((item) => item.id === selectedId ? { ...item, status: "down" } : item)). For this you needconst [items, setItems] = useState(equipment)in the browser component. - Prove immutability:
import { equipment } from "../data"inApp.tsxand logequipment.map((item) => item.status)after marking one down. The original array is unchanged. - Walk the whole feature with Tab and Enter only. Every control must be reachable and operable.
You are done when
Search, filter, and selection all work together; the source array logs unchanged; and you can name, for each piece of state, why it could not have been derived.
Common pitfalls
- Calling the handler instead of passing it.
onClick={setOpen(true)}runs during render and loops. WriteonClick={() => setOpen(true)}. - Reading state right after setting it. The variable belongs to the current render. Use the
updater form
setX((current) => ...)when the new value depends on the old. - Mutating then setting.
items.push(x); setItems(items);passes the same reference; React compares it with itself and skips the render. Build a new array. - Storing derived values. Keeping both
queryandfilteredItemsin state guarantees they drift apart. Compute during render.
Verify it yourself
Open today's reference, React's Learn section, and read the pages on state as a snapshot and on updating arrays in state.
- React describes a render's state as a snapshot. Find the sentence, then write what
setCount(count + 1)called three times in one handler produces, and why. - The arrays page lists which array methods are safe in state. Find where
sortandreverseland, and say why, given what Day 22 taught about mutation.
Put both answers in notes/day-59.md. The snapshot rule is the one that catches everyone, so
finding React's own wording for it is worth the ten minutes.
The hour
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Add search, status filter, and selected-equipment state.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
The list responds correctly without mutating source arrays.
Working with AI today
Define one small task, review the plan, inspect the diff, run checks, and explain every changed section.
Help me identify the minimal state. Challenge any value that could be derived.
References
End-of-day quiz
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.