Modern React 4: Events and local state
This is part 4 of the Modern React development series.
React components become interactive when event handlers record user intent and state remembers the values that affect rendering. The pair is small, but it explains buttons, menus, tabs, toggles, inline editors, and form controls.
Concept
An event handler is a function React calls after an event such as a click, change, submit, key press, or pointer movement. Local state is component memory managed with a Hook such as useState, and React renders again when that state changes.
Terms
- Event handler: A function passed to a JSX event prop such as
onClickoronChange. - Local state: State owned by one component instance.
- State setter: The function returned by
useStatethat schedules the next state value. - Render: React calling a component to calculate what the UI should look like for the current inputs.
Mental model
Treat each render as a snapshot. The JSX sees the state values from that render. Event handlers can request the next snapshot by calling a state setter.
How it is used
Use local state for details that belong to one component: whether a menu is open, which tab is selected, the current text in an input, or whether help text is visible. Use events to update that state in response to user intent.
How to use it
- Declare state at the top level of the component with
useState. - Pass a function to event props. Do not call the handler while rendering.
- Use functional state updates when the next value depends on the previous value.
- Move state upward only when another component needs to read or change it.
Example: Counter with functional updates
import { useState } from "react";
export function Counter() { const [count, setCount] = useState(0);
return ( <button onClick={() => setCount((current) => current + 1)}> Count: {count} </button> );}The updater receives the current queued value, which makes it the right shape when several updates can happen close together.
Example: Disclosure state
import { useState } from "react";
export function HelpDisclosure() { const [open, setOpen] = useState(false);
return ( <section> <button type="button" aria-expanded={open} onClick={() => setOpen((value) => !value)} > Shipping details </button> {open && <p>Orders ship within two business days.</p>} </section> );}The state belongs in the disclosure because no other component needs to own this open or closed memory.
Details to watch
- Passing handlers:
onClick={handleClick}passes a function.onClick={handleClick()}calls it during render. - Snapshots: Reading state after calling a setter still reads the value from the current render.
- Functional updates: Use
setValue((current) => next)when the next value depends on the current one. - State owner: State should live where the memory is used. Shared memory moves to the closest common parent.
Series navigation
- Previous: Part 3: Rendering lists and stable keys
- Next: Part 5: State shape and derived values
- Series index: Modern React development
References
- Responding to Events
- State: A Component’s Memory
- State as a Snapshot
- Queueing a Series of State Updates