When you call useState()
in React, you don’t pass any identifier or name. Yet React somehow remembers which state belongs to which variable between renders. It feels like magic, but it’s not. Let’s break it down from first principles using simple logic, examples, and a text-based diagram.
1. What React Is Doing Under the Hood
Every React component instance internally keeps two things:
- An array – stores all the hook states. Example:
componentHooks = [];
- An index counter – points to the next slot in that array.
currentHookIndex = 0;
Each time the component renders, React:
- Resets
currentHookIndex = 0
- Runs the component function top to bottom
- For every
useState()
call, React reads or creates the next slot in that array - Increments the index
That’s it. There’s no parsing, no magic, just arrays and indexes.
2. The First Render — Building the State Stack
Imagine a simple component:
function Counter() {
const [count, setCount] = useState(0);
const [text, setText] = useState("hello");
}
When this runs for the first time:
Step | Hook Call | Action | componentHooks | currentHookIndex |
---|---|---|---|---|
1 | useState(0) |
Slot empty → create [0, setCount]
|
[[0, setCount]] |
1 |
2 | useState("hello") |
Slot empty → create ["hello", setText]
|
[[0, setCount], ["hello", setText]] |
2 |
So after the first render:
componentHooks = [
[0, setCount],
["hello", setText]
]
currentHookIndex = 2
React now has a “stack” of hook slots — one for each useState()
call.
3. The Second Render — Reading from the Stack
When you call setCount(1)
, React updates that slot and re-renders the component.
Before rendering again:
componentHooks = [
[1, setCount],
["hello", setText]
]
currentHookIndex = 0
Now React runs the component again:
- First
useState(0)
→ returns[1, setCount]
from slot 0 - Second
useState("hello")
→ returns["hello", setText]
from slot 1
Because React always calls hooks in the same order, each slot stays matched to the correct state.
4. Text-Based Diagram: The Stack in Action
First Render:
┌────────────────────────────┐
│ Slot[0] = [0, setCount] │
│ Slot[1] = ["hello", setText] │
└────────────────────────────┘
After setCount(1) → Re-render:
┌────────────────────────────┐
│ Slot[0] = [1, setCount] │
│ Slot[1] = ["hello", setText] │
└────────────────────────────┘
React resets the index and walks through this “stack” again.
Each useState
call pulls the next slot in order.
5. Why Hook Order Matters
Let’s look at an incorrect example:
function BadCounter({ showText }) {
const [count, setCount] = useState(0);
if (showText) {
const [text, setText] = useState("hello"); // ❌ conditional hook
}
const [age, setAge] = useState(20);
}
Here’s what happens:
- On the first render (
showText = false
): Hook calls =[count, age]
- On the next render (
showText = true
): Hook calls =[count, text, age]
Now the slots shift — React can’t tell which state belongs to which variable.
That’s why React enforces this rule:
Hooks must always be called in the same order on every render.
This means no hooks inside if
, for
, or nested functions.
6. The Role of setState
Each setState
function is a closure over its own slot.
When you call setCount(newValue)
, it updates its pair:
pair[0] = newValue;
Then it tells React to re-render.
That’s how each setState
knows which piece of state to modify.
7. The Key Rules of Hooks
- ✅ Call hooks only at the top level of your component or custom hook
- ✅ Call them in the same order every render
- ✅ Use the React Hooks ESLint plugin to catch mistakes automatically
8. The Big Picture
All hook types (useState
, useEffect
, useRef
, etc.) work on the same principle:
React keeps an ordered list of hook data and an index pointer.
On every render, it reads or updates that list in the same sequence.
9. Final Summary
React’s Hooks are not magic.
They are just:
- Arrays (
componentHooks
) - Index counters (
currentHookIndex
) - Closures (
setState
functions)
This simple design lets React remember your state across renders with almost zero overhead.
In one line:
React’s
useState
works by walking through a fixed-order stack of state slots.
Each hook call picks the next slot. Stable order keeps your state stable too.
Top comments (0)