FS logo

Farhaan Shaikh

FS Blogs

HomeBlogsSubscribe

FS Blogs

Notes on building, shipping, and learning in public.

Essays, experiments, and honest lessons from the work behind this blog.

New posts, no noise.

One short email when something new is published.

No spam. Only new posts.

←Back to blogs

SetState feels broken

React handles state updates internally—why setState is asynchronous, how batching works, and why your state sometimes feels “wrong”.

🧩 Introduction

Blog image

That line is funny.

But it’s also technically accurate.

👉 Because React does NOT update state immediately.

And that’s where confusion starts.

⚠️ The Biggest Misconception

Blog image

Most developers think:

javascript

setCount(1);
console.log(count); // should be 1?

But React says:

👉 “Nope. Still 0.”

Why?

Because:

👉 State is not a variable

👉 It’s a request to React

What actually happens:

  1. You call setCount(1)
  2. React adds it to a queue
  3. React schedules a re-render
  4. THEN state becomes 1

👉 So the new value exists only after the next render

🧠 Core Mental Model

👉 setState = enqueue update, not assign value

If you remember just this:

You’ll avoid 50% of React bugs.

⚙️ React Is In Control (Not You)

Blog image

You might call:

javascript

setCount(5);
setName("Alice");
setLoading(false);

But React doesn’t immediately execute them.

👉 All of them go through something called:

🧠 React Scheduler

What it does:

  • Collects updates
  • Prioritizes them
  • Decides timing

👉 Example from the diagram:

  • Urgent → typing
  • Less urgent → data fetching

👉 This is why React feels “smart”

Because it is.

🔥 Batching — The Superpower

Blog image

What YOU expect:

javascript

setCount(c => c + 1); // render
setName("Alice");     // render
setLoading(false);    // render

👉 3 updates = 3 renders

What React does:

👉 Groups them → 1 render

Blog image

Flow:

  1. Event fires
  2. Updates collected
  3. Processed together
  4. Single re-render

👉 This is called batching

Why it matters:

  • Better performance
  • Fewer re-renders
  • Smoother UI

👉 Think:

“Batch first, render once”

💥 The Classic Bug (Most Important Part)

Blog image

javascript

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

Expected:

javascript

count = 2

Actual:

javascript

count = 1

👉 Why?

From the diagram:

What happens internally:

javascript

setCount(0 + 1); // queued
setCount(0 + 1); // queued again

👉 Both updates = same value

👉 React batches → applies once

Result:

plain text

count = 1

🧠 The Real Reason (Deep Insight)

Blog image

This is the BIGGEST mental shift.

Inside your function:

javascript

function handleClick() {
  console.log(count);
}

👉 count is NOT live

👉 It’s frozen at render time

Meaning:

  • It does NOT update mid-function
  • It does NOT reflect queued changes

👉 It’s just a snapshot.

One brutal truth:

🛠️ The Fix: Functional Updates

Blog image

javascript

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

Now what happens:

  1. First call → c = 0 → returns 1
  2. Second call → c = 1 → returns 2

👉 React uses latest queued value

Result:

plain text

count = 2 ✅

Why this works:

👉 So no stale data problem.

Golden Rule:

👉 Use functional updates when:

  • Updating based on previous state
  • Doing multiple updates
  • Inside loops or async logic

⚡ React 18 Upgrade (Important)

Blog image

Before React 18:

  • Batching only in event handlers
  • Not in setTimeout, promises, etc.

After React 18:

👉 Batching works everywhere

javascript

setTimeout(() => {
  setCount(c => c + 1);
  setName("Alice");
}, 1000);

👉 Still ONE render

Why?

Because of:

javascript

createRoot()

👉 This made batching universal.

🧠 Final Mental Model

Blog image

It tells React to schedule a render

Combine everything:

  • setState → queues update
  • React → schedules render
  • Updates → batched
  • State → updated on next render

🎯 Final Takeaways

  • setState is async
  • State is a snapshot, not live
  • Multiple updates are batched
  • React controls timing
  • Functional updates fix stale state

One-line clarity:

👉 React doesn’t update state instantly.

👉 It schedules it intelligently.

🔥 Mindset Shift

❌ “setState changes value immediately”

✅ “setState schedules a future render”

🚀 What You Just Learned

You now understand:

  • Why console logs show old values
  • Why double updates don’t stack
  • How batching works internally
  • Why React feels “delayed”
  • How to write correct state logic

🔜 Next Post Teaser

👉 How Hooks depend on this system

  • Why hook order matters
  • Why breaking rules crashes React
  • How React tracks hooks internally

If you want next level:

I can help you:

  • Convert this into a viral LinkedIn post (your style)
  • Add interactive diagrams for your blog
  • Or connect this with React Fiber internals

Just say 👍

Post details

Published

Apr 15, 2026

Updated

Mar 31, 2026

Read time

5 min read

Tags

ReactTechnicalBest PracticesGuideTutorial

Related posts

Why React Hooks Break?
ReactApr 17, 2026
Why React Hooks Break?Technical

Why React Hooks Break?

A deep dive into how React tracks hooks internally using an indexed system—and why breaking hook order causes bugs, crashes, and chaos.

Open article→
Using index as keys
ReactApr 13, 2026
Using index as keysTechnical

Using index as keys

Why using index as key causes subtle bugs, and how to fix it using stable identities.

Open article→
Re-renders are cheap
ReactApr 10, 2026
Re-renders are cheapTutorial

Re-renders are cheap

React avoids expensive DOM operations using diffing and reconciliation—and why most re-renders are not a performance problem.

Open article→