The First useState Hook
A click handler can log an action, but a log does not change the interface. The component needs a value that survives its current function call and causes another render when it changes.
React calls that stored component memory state. The useState Hook adds one state position to a component instance.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}The button begins at zero. Clicking requests one as the next count. React renders Counter again and the button receives the next text.
A Local Variable Fails Two Requirements
Start with a local variable.
function Counter() {
let count = 0;
function handleClick() {
count += 1;
}
return <button onClick={handleClick}>{count}</button>;
}The click changes the count binding captured by this render's handler. Two pieces are missing.
First, assigning the variable does not ask React to render the component again. The browser button keeps displaying zero.
Second, a later parent render calls Counter again and creates a new local count initialized to zero. The old function call has ended. Its local binding is not the current render's data.
State provides both requirements.
retain a value for this component instance
request another render after the value changesState is retained by React for a component instance. A state setter requests rendering with the next value.
Import the Hook
useState is a named export from the react package.
import { useState } from "react";Hooks are functions whose names begin with use. React associates their calls with the component instance currently being rendered.
Call useState inside the component body.
function Counter() {
const statePair = useState(0);
// ...
}The argument 0 is the initial state for this state position.
The function returns a two-position array.
position 0 -> current state value
position 1 -> state setter functionArray destructuring creates useful local names.
const [count, setCount] = useState(0);The array destructuring is JavaScript. React defines the returned pair.
Name the Value and Setter Together
Use a noun or boolean condition for the value and the same name after set for the setter.
const [count, setCount] = useState(0);
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);The pattern is a convention, not special parsing. These names would run.
const [banana, updateBanana] = useState(0);They fail to communicate the component state. count and setCount let a reader connect the stored value with its update function immediately.
Use an is, has, can, or show prefix for booleans when it makes the condition read directly.
const [showDetails, setShowDetails] = useState(false);{showDetails && <LessonDetails />}The Setter Requests the Next Value
Call the setter from the click handler.
function handleClick() {
setCount(count + 1);
}count + 1 is calculated from the count visible to this render. The setter queues that result as the next state.
The setter does not return the next state for immediate use.
const result = setCount(count + 1);result is undefined. State is available to the next render, not as a setter return value.
Do not assign to the state binding.
count = count + 1;The binding was declared with const, and an assignment would not update React's retained state. Use the setter.
Calling a setter queues an update. It does not replace the local state variable in the function call already running.
Follow the First Render
Use the complete component.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return <button onClick={handleClick}>{count}</button>;
}On the first render, React reaches the Hook call and initializes its state position with zero. The local count binding receives zero. React returns a stable setter for that position through setCount.
The returned button displays zero and stores handleClick in its event prop. The handler closes over the count binding from this render.
After the click, setCount(1) requests another render. During that render, React does not initialize the position again. It supplies the stored value one.
first render -> count 0
click -> request count 1
next render -> count 1The component function starts again, but React retains the state position for that rendered instance.
State Is Stored per Component Instance
Render two counters.
function App() {
return (
<main>
<Counter />
<Counter />
</main>
);
}Both occurrences use the same Counter function definition. React creates two component instances at two positions. Each instance receives its own state position.
Clicking the first counter updates the first instance. The second remains at zero.
Counter instance at position 0 -> count 1
Counter instance at position 1 -> count 0State is not stored in a module variable on the function. React associates it with the instance in the rendered tree.
Moving a component to another position or replacing its type can therefore end the instance associated with its state.
One Component Can Have Several State Positions
A details component can store open state and a selected tab.
function LessonDetails() {
const [isOpen, setIsOpen] = useState(false);
const [tab, setTab] = useState("notes");
// ...
}React associates the first Hook call with one state position and the second Hook call with another.
The order must remain the same across renders.
Hook call 1 -> isOpen
Hook call 2 -> tabThis order is the basis for the Rules of Hooks.
Use separate state variables when values change independently. An object can group related fields later, but it adds replacement and copy behavior that a single boolean does not need.
Follow the Rules of Hooks
Call Hooks at the top level of a function component or another Hook. Do not call them inside conditions, loops, event handlers, or nested ordinary functions.
This condition changes the Hook order.
function LessonDetails({ available }) {
if (available) {
const [isOpen, setIsOpen] = useState(false);
}
// ...
}One render can call useState while another skips it. React can no longer associate later Hook calls with consistent positions.
Call the Hook first and branch afterward.
function LessonDetails({ available }) {
const [isOpen, setIsOpen] = useState(false);
if (!available) {
return null;
}
// ...
}This loop is invalid for the same reason.
lessons.map(() => {
const [open, setOpen] = useState(false);
});Extract a LessonItem component and let each rendered instance call its own Hook at component top level.
Hook calls must keep the same order for a component across renders.
The React Hooks ESLint plugin reports many order violations before runtime. Keep its rules enabled.
Do Not Call Hooks from Event Handlers
This handler runs after render, when React is not performing the component's Hook call sequence.
function handleClick() {
const [count, setCount] = useState(0);
}React reports an invalid Hook call. State must be declared while the component renders.
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}The handler closes over the setter and current render's value.
Initial State Is Used for Initialization
This Hook receives zero during every component function call in source.
const [count, setCount] = useState(0);React uses the argument when initializing that state position. Later renders use the retained state, not the argument again.
This becomes visible with a prop.
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
// ...
}Changing initialCount after the component is mounted does not automatically reset count. The prop names an initial value, not a synchronization rule.
If the component should always display the prop, it may not need local state. If another record should start a fresh instance, give that rendered component a different stable key.
Use a Lazy Initializer for Expensive Creation
Suppose initial state requires a non-trivial local calculation.
function createInitialLessons() {
return readSavedLessons();
}Pass the function itself.
const [lessons, setLessons] = useState(createInitialLessons);React calls it when initialization is required. This version calls it during every render before React can ignore later initial arguments.
const [lessons, setLessons] = useState(createInitialLessons());Use lazy initialization only when the calculation has enough cost to justify it. A primitive literal needs no initializer function.
The initializer must be pure. Development Strict Mode can call it twice and ignore one result to expose accidental mutation.
const [count] = useState(() => 0);This works but adds syntax without saving work. Use useState(0).
State Can Hold JavaScript Values
These initial values are all supported.
useState(0);
useState("");
useState(false);
useState(null);
useState([]);
useState({ query: "", status: "all" });React stores the value you supply. It does not validate a domain contract or make objects immutable.
Start with a primitive when the interaction needs one primitive. Object and array state must be replaced with new references when it changes.
Do not store a React component function in state without a specific design. Passing a function directly to a setter can be interpreted as an updater function. Storing callable values needs an extra wrapper and is uncommon in application code.
Store Only the Independent Value
This component needs one boolean.
const [isOpen, setIsOpen] = useState(false);The label follows from it.
const label = isOpen ? "Hide details" : "Show details";Do not store both.
const [isOpen, setIsOpen] = useState(false);
const [label, setLabel] = useState("Show details");Every event would need to update two values that describe one fact. Calculate the second value from the first instead.
Build the First Stateful Component
Create a details control.
function ChapterDetails() {
const [isOpen, setIsOpen] = useState(false);
function handleClick() {
setIsOpen((open) => !open);
}
return (
<section>
<button
type="button"
aria-expanded={isOpen}
onClick={handleClick}
>
{isOpen ? "Hide details" : "Show details"}
</button>
{isOpen && <p>This chapter contains six lessons.</p>}
</section>
);
}The updater function receives the queued current value and returns its opposite. Use this form whenever the next state depends on the previous state.
Click several times and inspect the component in React DevTools if installed. The state entry changes between false and true while the component type and position remain stable.
Lesson Check
The first state path has five distinct parts.
useState declares one state position
React supplies the current value and setter
the rendered handler closes over them
the interaction calls the setter
React renders the instance with the next stored valueSource Notes
- React reference,
useState - React documentation, State as Component Memory
- React documentation, Rules of Hooks
- React reference,
StrictMode