Use State
Props let a parent hand data down to a child, but they're read-only from the child's side. State is the other half of the picture — data a component owns and can change itself, in response to things like a click or a timer. Whenever a piece of state changes, React automatically re-renders that component (and re-runs the virtual DOM comparison from Chapter 1) to reflect the new value.
The useState Hook
useState(0) creates one piece of state, starting at 0, and returns an array with exactly two things: the current value (count) and a setter function (setCount) used to change it. Calling setCount(count + 1) tells React "this component's state changed, please re-render it" — the component function runs again, this time with count holding the new value.
use are called hooks — special functions that "hook into" React features like state. They must be called directly inside a component function (or another hook), always at the top level — never inside an if, a loop, or a nested function. This guide will introduce several more hooks across the next few chapters; the same rule applies to all of them.
Why Not Just a Regular Variable?
A plain let count = 0 would actually increment in memory each click, but React has no reason to re-render the component — nothing told it the UI needs updating, so the displayed number never changes on screen. setCount exists specifically to trigger that re-render; a regular variable reassignment is invisible to React.
Updating Based on the Previous Value
Passing a function to the setter (instead of a value directly) receives the most up-to-date previous value as its argument. This matters when several updates might happen close together — React guarantees each functional update sees the result of the one before it, whereas calling setCount(count + 1) twice in a row can both read the same stale count and only increment once instead of twice.
Multiple Independent State Variables
A single component can call useState as many times as it needs — each call manages a completely independent piece of state, with its own value and its own setter. There's no requirement to bundle everything into one big state object the way some older patterns did; usually several small, clearly-named pieces of state read better than one combined one.
user.name = "New Name" directly on a state object (instead of calling its setter with a new object) won't trigger a re-render, and risks subtle bugs even when it happens to work visually. Always treat state as read-only outside of its setter — for objects and arrays, that means creating a new copy with the change applied, rather than editing the existing one in place. This becomes especially important in Intermediate Chapter 3 (useReducer) and is worth getting into the habit of now.
| Pattern | When to use it |
|---|---|
| setCount(5) | Setting state to a known, fixed value |
| setCount(count + 1) | Simple update based on the current render's value |
| setCount(prev => prev + 1) | Safer update when multiple changes might happen in quick succession |
Coding Challenges
Build a Counter component with +1 and -1 buttons, both updating the same count state, and a "Reset" button that sets count back to 0.
📄 View solutionBuild a LightSwitch component with a boolean isOn state (starting false) and a single button that toggles it, displaying "On" or "Off" based on the current value.
📄 View solutionBuild a component with two separate pieces of state: a numeric clickCount, and a string lastClicked storing the current time (use new Date().toLocaleTimeString()). One button updates both at once when clicked.
📄 View solutionChapter 3 Quick Reference
- useState(initialValue) — returns
[value, setValue]for one piece of state - Calling the setter triggers a re-render — a plain variable reassignment does not
- Functional updates —
setCount(prev => prev + 1)— safer when updates happen close together - A component can call
useStatemultiple times for independent pieces of state - Never mutate state directly — always go through its setter
- Hooks (functions starting with
use) must be called at the top level of a component, never conditionally - Next chapter: event handling — the onClick pattern used here, covered properly across more event types