REACT: Under the Hood of UseState

Omm PaniOmm Pani
12 min read

Did you know? React doesnโ€™t store state in variables.

When you call:

const [count, setCount] = useState(0);

You might assume React stores count in some internal object or variable tied to your component.

But thatโ€™s not how it works.

โœ… React actually tracks state using:

  • A linked list of hook nodes

  • A circular queue of updates

  • And a strict render-time hook call order

Itโ€™s not magic โ€” itโ€™s just clever bookkeeping.

Letโ€™s lift the hood and walk through how useState works โ€” with real examples

useState Isnโ€™t Just a Variable โ€” Itโ€™s a Slot in a List

const [count, setCount] = useState(0); //slot 0
const [text, useText] = useState(""); //slot 1

At first glance, this looks like a variable initialized with 0.
But in React land, this means:

โ€œHey React, on this render, I want the state stored at slot #0 in this componentโ€™s hook list.โ€

Yes โ€” slot 0. Not a name, not an ID โ€” just a position in a list. Like this:

So how does useState(0) really work?

Hereโ€™s what React does behind the scenes ๐Ÿ‘‡

๐Ÿ”ข React assigns a slot based on the order of hook calls
๐Ÿ”— It stores the value 0 in a linked list of hooks(per component)

Component Fiber
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ hooks (linked list)        โ”‚
โ”‚                            โ”‚
โ”‚ Hook 0 โ”€โ–ถ Hook 1 โ”€โ–ถ null   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Each hook โ€” whether itโ€™s useState, useReducer, or others โ€” becomes a node in a linked list attached to the componentโ€™s Fiber node.

Why a list and not an array? Because:

  • Hook count can vary

  • Lists allow predictable traversal

  • And React re-creates this list fresh on every render, based on call order

๐Ÿ˜ฑ Why You Canโ€™t Call Hooks Conditionally

Ever seen this?

โš ๏ธ โ€œRendered fewer hooks than expected.โ€

Thatโ€™s React telling you:

โ€œHey, your hook list is broken. I canโ€™t align state to the right slots anymore.โ€

Example:

if (isLoggedIn) {
  useState("hi"); // โŒ DON'T
}

This messes up the hook slots. On next render, useState("hi") might become slot 0 instead of slot 1.
Now every hook is getting the wrong state.

๐Ÿ”ฅ TL;DR: Hook call order = sacred. Never call hooks conditionally.

๐Ÿง  Real-World Takeaway

This explains bugs like:

  • Stale state

  • Hooks acting "out of sync"

  • setState updating the wrong value (or nothing at all)

React doesnโ€™t remember variable names โ€” it relies entirely on hook call order during render.


What Happens When You Call setCount(...)?

So far, weโ€™ve seen how useState assigns a slot in a linked list to hold your state.

But what happens when you actually update that state?

setCount(c => c + 1);

You might expect the state to update immediately โ€” but React doesnโ€™t do that. Instead, it pushes the update into a queue, and schedules a re-render.

๐Ÿ“ฆ When setCount(newValue) is called:

๐Ÿ” React schedules a re-render
๐Ÿง  During render, it goes back to the same slot (slot 0)
๐Ÿ‘‰ Retrieves the new updated state
๐Ÿงฉ Then it compares ๐Ÿ”„ the old and new virtual DOM
๐ŸŽจ If thereโ€™s a change, React updates the UI

This flow is what powers Reactโ€™s magic โ€” hooks + virtual DOM + Fiber working together.

๐Ÿ” React uses a circular linked list of updates

Each hook node (like Hook 0 for count) has an internal queue that holds pending updates.

Each hook is a node in a linked list ๐Ÿ”—
Hereโ€™s what each node holds:
๐Ÿง  memoizedState โ†’ the actual state value
๐Ÿ“ฅ queue โ†’ list of pending setState calls
โญ๏ธ next โ†’ pointer to the next hook in this component
This structure is rebuilt on every render โ€” in the same order!

Letโ€™s visualize it.

Hook 0
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState:               โ”‚ 0
โ”‚ queue.pending:               โ”‚ null
โ”‚ next:                        โ”‚ โ†’ Hook 1
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hook 1
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState:               โ”‚ ""
โ”‚ queue.pending:               โ”‚ null
โ”‚ next:                        โ”‚ โ†’ null
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Now you call:

setCount(c => c + 1);

๐Ÿ“ฌ React pushes an update into the queue

Hook 0
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState                โ”‚ 0
โ”‚ queue.pending                โ”‚ โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ next                         โ”‚               โ†“
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                         โ”‚ action: f (c + 1)  โ”‚
                                         โ”‚ next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ (points to self)
                                         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hook 1 (unchanged)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState                โ”‚ ""
โ”‚ queue.pending                โ”‚ null
โ”‚ next                         โ”‚ null
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŒ€ React uses a circular list here โ€” Because thereโ€™s only one update, next points to itself forming a circular list with one item. โ€” so update.next points to itself if it's the only item.

Why circular? Because it's fast to:

  • Add new updates

  • Walk the queue in order

  • Wrap around without null-checks


โœ… What Happens on Re-render?

During the next render cycle:

  1. React reaches Hook 0 again

  2. Sees there's a queue.pending

  3. Walks the circular list and applies all updates in order

  4. Updates memoizedState

  5. Clears the queue

Example:

// Initial state
memoizedState = 0

// apply setCount(c => c + 1)
memoizedState = 1

// clear queue
queue.pending = null

Boom โ€” count becomes 1 in the next render. ๐Ÿง 

๐Ÿ’ก Real-World Takeaway

  • State updates are async because React batches them in queues

  • setState doesnโ€™t update immediately โ€” it just pushes to the queue

  • You can have multiple queued updates before the next render

This explains:

  • Why console.log(count) right after setCount(...) shows stale data

  • Why batching works (like in event handlers or startTransition)

  • Why setState inside loops can behave unexpectedly


What if multiple setCount calls happen before React re-renders?

Letโ€™s say you run this in an event handler:

setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);

These donโ€™t immediately re-render. Instead, React queues them up in a circular linked list inside Hook 0:

Hook 0 (count)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState: 0           โ”‚
โ”‚ queue.pending              โ”‚ โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ†“
       โ–ฒ                             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ update 1     โ”‚
                                     โ”‚ action: f   โ”‚
                                     โ”‚ next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ–ถ update 2
                                     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                               โ†“
                                     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                     โ”‚ update 2    โ”‚
                                     โ”‚ action: f   โ”‚
                                     โ”‚ next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ–ถ update 3
                                     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                               โ†“
                                     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                     โ”‚ update 3    โ”‚
                                     โ”‚ action: f   โ”‚
                                     โ”‚ next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ†’ points to update 1
                                     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  (last update links back to first)
       โ–ฒ                                 โ”‚      
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”„ Updates are queued as a circular list:
each setCount adds a node, and React later walks through all of them in order.


โš™๏ธ During the next renderโ€ฆ

React performs this:

  1. Starts from memoizedState = 0

  2. Applies update 1: c => c + 1 โ†’ 1

  3. Applies update 2: c => c + 1 โ†’ 2

  4. Applies update 3: c => c + 1 โ†’ 3

  5. Sets memoizedState = 3

  6. Clears the queue.

๐ŸŽฏ Final count value will be 3, not 1 โ€” because all updates used the latest state (c => c + 1).


โœ… Why Devs Should Care

  • setCount(x + 1) (non-functional) would overwrite and only apply once.

  • setCount(c => c + 1) stacks โ€” each update gets applied.

  • You donโ€™t need useEffect() tricks for batching; Reactโ€™s queue handles this.

  • React batches updates and avoids redundant renders efficiently using this structure.

  • If you queue multiple updates using functional form (c => c + 1), React handles them correctly.

  • If you use non-functional form (setCount(count + 1)), they may overwrite each other. โš ๏ธ


๐Ÿงฌ How React Processes Updates (Behind the Scenes Revisit)

When a re-render is triggered (due to setState), React walks the list of hooks in the exact same order as before.

Each hook looks like this internally:

HookNode {
  memoizedState: current state value,
  queue: {
    pending: circular linked list of updates
  },
  next: โ†’ next HookNode
}

Letโ€™s revisit this example:

const [count, setCount] = useState(0); // slot 0
const [text, setText] = useState("");  // slot 1

React builds a linked list of hooks for this component:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Hook 0       โ”‚ โ”€โ”€โ–ถ โ”‚ Hook 1       โ”‚ โ”€โ”€โ–ถ null
โ”‚ count = 0    โ”‚     โ”‚ text = ""    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Letโ€™s say setCount(c => c + 1) was called 3 times.

During the next render:

  1. React walks hook list โ†’ starts with Hook 0

  2. Sees updates in queue:

    • Applies all 3 c => c + 1 updates

    • Final value becomes 3

  3. Moves to Hook 1 โ†’ No updates โ†’ Skips.

Hook 0 (updated)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState: 3   โ”‚
โ”‚ queue.pending: nullโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hook 1 (unchanged)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState: ""  โ”‚
โ”‚ queue: null        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿง  Real-World Mental Model

React doesnโ€™t "remember" state via variable names.
It tracks state slot-by-slot via hook order. Hook 0 will always be count.

Changing the order of hooks between renders?
๐Ÿ’ฅ Boom โ€” React throws:
"Rendered fewer hooks than expected..."


๐Ÿงต Just Enough Fiber: How React Knows Which Hook Is Which

React's secret sauce? A data structure called Fiber.

Each component gets its own Fiber Node โ€” a JavaScript object that holds everything React needs to track during rendering.

Hereโ€™s what a simplified Fiber looks like:

FiberNode {
  memoizedState โ†’ points to head of hook list
  return โ†’ parent fiber
  child โ†’ first child
  sibling โ†’ next sibling
  ...
}

๐ŸŽฏ memoizedState is the key.

React stores the head of the hook linked list on the component's Fiber.

As your component renders, React walks that list one hook at a time โ€” in order.


๐Ÿ“ฆ During First Render:

For our example:

const [count, setCount] = useState(0); // slot 0
const [text, setText] = useState("");  // slot 1

React does:

Fiber.memoizedState โ”€โ”€โ–ถ Hook 0 โ”€โ”€โ–ถ Hook 1 โ”€โ”€โ–ถ null
                       count=0    text=""

Each useState() call creates a Hook node and links it to the previous one.

๐Ÿ” During Re-render:

Same order. No surprises.

  1. React sets a pointer to Fiber.memoizedState

  2. Every time a hook is called, React moves to the next hook node

  3. It retrieves memoizedState, applies any updates, and moves on

Fiber.memoizedState โ”€โ–ถ Hook 0 โ”€โ–ถ Hook 1
(current hook pointer walks one by one)

๐Ÿ’ก Thatโ€™s why hook order must be consistent across renders. If it changes, React can't match the state correctly.


โš™๏ธ Real-World Framing

  • Want to build custom hooks? Now you know they're just functions called in order.

  • Using React DevTools and seeing Hooks (count, text)? Thatโ€™s this list.

  • Got a bug with mismatched state? Might be breaking hook order internally.


๐Ÿ” Visual Recap: The useState Flow, Start to Finish

Let's put the whole story together with ASCII diagrams:

๐Ÿงฑ Initial Render:

const [count, setCount] = useState(0); // slot 0
const [text, setText] = useState("");  // slot 1

React:

Fiber.memoizedState
      โ”‚
      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Hook 0        โ”‚โ”€โ”€โ”€โ–ถโ”‚ Hook 1        โ”‚โ”€โ”€โ–ถ null
โ”‚ state: 0      โ”‚     โ”‚ state: ""     โ”‚
โ”‚ queue: null   โ”‚     โ”‚ queue: null   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โฉ After setCount(c => c + 1):

Hook 0
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState                โ”‚ 0
โ”‚ queue.pending                โ”‚ โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚               โ†“
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                         โ”‚ update.action: f   โ”‚
                                         โ”‚ update.next โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ (points to self)
                                         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hook 1 (unchanged)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState: ""  โ”‚
โ”‚ queue: null        โ”‚
โ”‚ next: null         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” During Re-render:

  1. React walks the hook list via next

  2. Applies each hookโ€™s queue

  3. Updates memoizedState

  4. Resets queue to null

Hook 0
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ memoizedState                โ”‚ 1 โœ…
โ”‚ queue.pending                โ”‚ null
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hook 1 stays unchanged.

๐Ÿง  TL;DR โ€” How useState Really Works

  • Hooks form a linked list stored on the componentโ€™s Fiber

  • useState() allocates a slot based on call order

  • setState() pushes updates into a circular queue

  • On re-render, React:

    • Walks the hook list

    • Applies all pending updates

    • Updates memoizedState

  • Fiberโ€™s memoizedState โ†’ head of the hook list

  • ๐Ÿ’ฅ Hook order must never change


โญ๏ธBONUS: setCount(count + 1) vs setCount(c => c + 1)โš”๏ธ

Letโ€™s say count = 0, and you write:

setCount(count + 1);
setCount(count + 1);
setCount(count + 1);

You might expect count to become 3, but insteadโ€ฆ it becomes 1.

โ— Why?

count + 1 is evaluated immediately, not lazily.

So each line becomes:

jsCopyEditsetCount(1); // because count is still 0
setCount(1);
setCount(1);

Only the last one wins.


โœ… Instead, use the functional form:

setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);

Each call receives the latest state, even if queued.

So:

0 โ†’ 1 โ†’ 2 โ†’ 3 โœ…

๐Ÿงช Real-World Example

In a counter button:

jsCopyEditonClick={() => {
  setCount(count + 1); // โŒ always sets to 1 if clicked fast
}}

onClick={() => {
  setCount(c => c + 1); // โœ… properly increments
}}

๐Ÿ’ก Takeaway

  • Use setCount(prev => prev + 1) when the next state depends on the previous.

  • Especially important when:

    • Scheduling multiple updates

    • React batches them

    • Working with async effects


๐Ÿš€ In the Endโ€ฆ

Why Should You Care?

This wasnโ€™t just a nerdy dive into React internals โ€” it has real-world impact:

โœ… Fix bugs you didnโ€™t know existed
Understanding that setState is async and batched helps you avoid subtle bugs in counters, toggles, and complex UI flows.

๐Ÿ” Predict re-renders confidently
Knowing how hooks are matched by order and updates are queued means you can refactor without fear.

๐ŸŽฏ Write faster, safer components
Use functional updates where needed. Avoid unnecessary state. Debug smarter.


๐Ÿง  TL;DR

React doesnโ€™t โ€œstoreโ€ state in variables.

It:

  • ๐Ÿ”— Uses a linked list of hooks per component.

  • ๐Ÿ“ Assigns each useState a slot based on order.

  • ๐Ÿ”„ Queues updates via a circular linked list.

  • ๐Ÿงฎ Applies updates during re-render using the Fiber architecture.

const [count, setCount] = useState(0); // slot 0
const [text, setText] = useState("");  // slot 1

Behind the scenes:

  • count = hook 0 โ†’ memoized state = 0

  • setCount(x) โ†’ pushes an update to hook 0's queue

  • During next render: React runs reducer logic, gets the new state

  • Updates memoized state, clears queue


๐Ÿคฏ Final Thought

Reactโ€™s useState isnโ€™t just a simple getter/setter โ€” itโ€™s a finely tuned dance of linked lists, update queues, and render orchestration.

Next time you write setCount(c => c + 1) โ€” know that somewhere deep in Fiber, a tiny linked list is hard at work.


0
Subscribe to my newsletter

Read articles from Omm Pani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omm Pani
Omm Pani