Topic 4.2
State: A Component's Memory
In one line
useState gives a component a value that React remembers between renders plus a setter function; calling the setter stores a new value and makes React render the component again so the screen shows it.
Think of it like this
A whiteboard in a classroom. The teacher (React) wipes the room clean every lesson (every render), so anything written on scraps of paper (local variables) is thrown away. But the whiteboard stays: whatever is written there is still there next lesson. State is the whiteboard: React keeps it for your component between renders, and when someone writes something new on it, the class gets a fresh lesson (a re-render) that uses the new value.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- State
- Data a component remembers between renders. Changing it makes the component render again.
- Hook
- A React function whose name starts with
use(likeuseState) that adds a React feature to a component. - useState
- The Hook that adds a state variable:
const [value, setValue] = useState(initial). - Initial state
- The value passed to
useState, used only on the first render. - Setter (set function)
- The second thing
useStatereturns. Calling it stores a new value and asks React to re-render. - Re-render
- React calling your component function again to get an updated description of the screen.
- Instance
- One rendered copy of a component.
<Counter />twice makes two instances with separate state. - Array destructuring
const [a, b] = array: naming the items of an array in one line.
Step by step
01Start with the broken version
This gallery tries to remember the picture number in a local variable. The click handler changes it, but the screen never moves.
function Gallery() {
let index = 0; // created as 0 on every render
function handleClick() {
index = index + 1; // changes a variable React doesn't watch
}
return <button onClick={handleClick}>Picture {index + 1}</button>;
}02Import useState
useState is a named export of the react package.
import { useState } from "react";03Declare a state variable
Replace the local variable with a state variable. index is the current value; setIndex is how you change it. 0 is used only the first time.
const [index, setIndex] = useState(0);04Call the setter in the handler
Instead of changing index, ask React to store a new value. React renders Gallery again, useState returns 1, and the button shows Picture 2.
function Gallery() {
const [index, setIndex] = useState(0);
function handleClick() {
setIndex(index + 1);
}
return <button onClick={handleClick}>Picture {index + 1}</button>;
}05What happens on each click
Your setter call is the trigger. React re-runs the component with the stored value and updates the DOM to match.
06Add more state, and more instances
Add a second variable for a show/hide toggle. Then render two galleries: each keeps its own index and showMore, because state belongs to the instance.
Try it yourself
- 1
Count backwards too
In the gallery, add a
Previousbutton. Predict: what should happen at the first place? Make it wrap to the last place. - 2
Add a third counter
In the last example, add
<Counter label="Kiwis" />. Predict its starting number and whether clicking it changes the others. - 3
Change the initial state
In the first example, change
useState(0)touseState(10). Predict the first line in the console. Then think: if you changed it touseState(count + 100), why would that not work? (countdoesn't exist yet whenuseStateis called.)
Code & diagrams
The handler does change `clicks` to 1, but the next render starts it at 0 again, and the screen shows 0. Only `count`, the state, survives each render.
Preview
Console
Nothing logged yet.Expected console output
render: clicks = 0 | count = 0
handler: clicks is now 1
render: clicks = 0 | count = 1
handler: clicks is now 1
render: clicks = 0 | count = 2`index` and `showMore` are independent: moving to the next place keeps the details open. Click Next a few more times and watch it wrap around.
Preview
Console
Nothing logged yet.Expected console output
Hawa Mahal in Jaipur | Has 953 small windows called jharokhas.Same component, two instances, two separate counts. Neither counter can see the other's state, and App can't see either.
Preview
Console
Nothing logged yet.Expected console output
Apples: 2
Mangoes: 1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Update the variable instead of calling the setter
In the gallery, change handleNext to index = index + 1; (and declare let [index, setIndex] = useState(0) so it compiles).
Break #2
Expect the initial value to reset the state
Pass useState(label.length) in Counter and later change the label prop from the parent.
Myth vs fact
Myth
setCount(5) changes count to 5 right away.
Fact
It asks React for a new render in which count will be 5. The count in the code that's running keeps its old value (Topic 4.4).
Myth
State is shared by every copy of a component.
Fact
Each rendered instance has its own state. To share, lift the state to a common parent (Topic 6.3) or use context (Phase 9).
Myth
Every variable in a component should be state.
Fact
Use state only for data that changes over time and must be shown. Values you can compute from props or other state should be computed during render, not stored (Topic 6.1).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
State lives in the component's fiber (React's internal record for that instance) as a linked list of hook slots, in call order.
useStatereads the next slot each time it's called. That's the mechanism behind the Rules of Hooks (Topic 4.3) and why state is tied to a position in the tree, not to a variable name. - ▸
Lazy initial state:
useState(() => loadDraftFromStorage())passes a function; React calls it only on the first render.useState(loadDraftFromStorage())would run the expensive call on every render and throw the result away. - ▸
If you set state to a value that is the same as the current one (compared with
Object.is), React skips re-rendering the children. It may still call your component once before bailing out, which is harmless if your component is pure. - ▸
The setter function's identity is stable: it's the same function on every render, so you can safely leave it out of effect and memo dependency lists (Phase 7).
Remember this
- 1
Why a normal variable doesn't work: a component is a function, and React calls it again on every render. A
let count = 0inside it is created fresh, as0, on every call, so changes are lost. And changing a local variable doesn't tell React anything, so React doesn't render again anyway. You need two things: a value that survives between renders, and a way to ask React to render again.useStategives you both. - 2
useStateis a Hook: a special React function whose name starts withuseand that lets a component "hook into" React features. You call it at the top of your component:const [count, setCount] = useState(0);. The argument (0) is the initial state, used only on the very first render. It returns an array of exactly two things, which you name with array destructuring: the current value and a setter function. The convention is[something, setSomething]. - 3
Calling the setter, like
setCount(5), does two things: it tells React to store the new value, and it triggers a re-render. React then calls your component again; this timeuseState(0)ignores the0and returns the stored5. The new JSX shows5, and React updates the page. The setter does not change thecountvariable you already have in the running code (Topic 4.4 explains why). - 4
A component can have as many state variables as it needs, each with its own
useStatecall:const [index, setIndex] = useState(0);andconst [showMore, setShowMore] = useState(false);. State can hold any JavaScript value: numbers, strings, booleans,null, objects and arrays (Topics 4.6 and 4.7 show how to update those). Keep unrelated values in separate variables; group values that always change together (Topic 6.1). - 5
State is private to each instance. Render
<Counter />twice and you get two independent counters: each copy has its own state. The parent can't read or change a child's state; if a parent needs the value, the state moves up into the parent and goes down as props (Topic 6.3, lifting state up). React keeps each piece of state attached to the component's position in the tree, not to the function or a variable name. - 6
How does React know which state belongs to which
useStatecall? A component doesn't pass any id. React counts: the firstuseStatecall in a render gets the first saved value, the second call gets the second, and so on. That is why hooks have rules about where you may call them (Topic 4.3).
Explain it without notes
Why can't a component remember a value in a local variable?
What does useState return, and what does calling the setter do?
If you render a component three times, how many copies of its state exist?
When is the argument to useState used?
Practice
Build a Toggle component with a button that switches between ON and OFF on each click.
Build a NameTag with two buttons, Asha and Ravi, and a heading Hello, <name>! that starts as Hello, guest!.
Trade-offs
- ↔
Many small
useStatecalls are simple to read; when several values always change together or the update logic grows, one object or a reducer (Phase 6) keeps them consistent. - ↔
Local state keeps a component self-contained, but state another component needs must be lifted up, which spreads props through the tree (Phase 6 and Phase 9).
Done when you can
I can explain why a local variable can't hold a component's memory.
I can declare state with
const [x, setX] = useState(initial).I can update state with its setter and know that triggers a re-render.
I can use several state variables in one component.
I know each component instance has its own private state.