DEV Community

Safal Bhandari
Safal Bhandari

Posted on

How React’s useState Really Works — A Simple Explanation from First Principles

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:

  1. An array – stores all the hook states. Example:
   componentHooks = [];
Enter fullscreen mode Exit fullscreen mode
  1. An index counter – points to the next slot in that array.
   currentHookIndex = 0;
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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] │
 └────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)