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

React ran my useEffect twice

Why React runs your effects twice in development, what Strict Mode is actually doing behind the scenes, and how it helps you write safer, predictable code.

🧩 Introduction

Blog image

Something strange happened in my React app.

I wrote a simple useEffect.

It had an empty dependency array.

It should run only once.

But it ran twice.

At first, I thought:

“Is React buggy?”

“Why is this happening?”

Turns out…

👉 React did it on purpose.

And once you understand why,

you’ll start appreciating React at a completely different level.

This blog is not just about fixing that issue.

It’s about understanding how React thinks internally.

⚠️ The Situation Every Developer Faces

Blog image

Let’s start with the exact code:

typescript

useEffect(() => {
	console.log("Effect ran");
}, [])

Expected output:

plain text

Effect ran

Actual output (in development):

plain text

Effect ran
Effect ran

This is where confusion begins.

You wrote correct code.

Your dependencies are correct.

There is no loop.

Still…

👉 Why twice?

🔍 First Important Truth

This behavior only happens in development mode.

Not in production.

So React is not “breaking your app” in production.

Instead…

👉 React is testing your code during development.

🧪 The Real Reason

Blog image

React wraps your application (by default in many setups) with something called:

Strict Mode

typescript

<React.StrictMode>
	<App/>
</React.StrictMode>

Now here’s the key:

👉 In Strict Mode, React intentionally runs certain functions twice.

This includes:

  • Component render
  • useEffect
  • Cleanup functions

🧠 Why Would React Do That?

Because React wants to catch bugs that are:

👉 Hidden

👉 Hard to reproduce

👉 Dangerous in real-world apps

Think of it like this:

It doesn’t just check if your code works once.

It checks:

👉 “Will this still behave correctly under stress?”

🔁 What Actually Happens Internally

When your component mounts, React does something like this:

plain text

1. Render component
2. Run useEffect
3. Run cleanup (if exists)
4. Re-render component
5. Run useEffect again

This is NOT random.

It’s a deliberate simulation.

💡 Analogy (This Will Make It Click)

Imagine you built a bridge.

You test it by:

  • Walking once → it works
  • But then…

A real engineer will:

👉 Shake it

👉 Load it

👉 Stress test it

Why?

Because real-world conditions are unpredictable.

React is doing the same thing.

👉 It is stress-testing your component.

⚙️ What React Is Actually Testing

Blog image

React expects your components to behave like:

✅ Pure Functions

A pure function means:

  • Same input → Same output
  • No side effects
  • No external changes

Example:

typescript

functionadd (a, b) {
	return a + b;
}

Always predictable.

❌ What React Hates: Side Effects

A side effect is when your component:

  • Modifies external variables
  • Talks to outside systems without control
  • Changes something React doesn’t track

Example:;

typescript

let counter = 0;

function App() {
	counter++;
}

Now this is dangerous.

Because:

👉 React does NOT control counter

💥 The Hidden Bug (Why Double Run Helps)

Blog image

Let’s analyze:

typescript

let counter = 0;

function App() {
	counter++;
	console.log(counter);
}

Output:

plain text

1
2

Why?

Because React rendered twice.

The real issue:

Your component is not isolated anymore.

It is affecting global state.

Why this is dangerous in real apps:

  • Multiple renders → inconsistent data
  • Bugs that only appear in production
  • Hard-to-debug behavior

🔥 Key Insight:

If your code behaves differently when run twice…

👉 It was already broken.

React just exposed it early.

✅ The Correct Mental Model

👉 Components should NOT depend on external mutable values.

Instead:

👉 Let React manage everything.

🛠️ The Correct Approach (Using State)

Blog image

typescript

function App() {
	const [counter, setCounter] = useState(0);

	useEffect(() => {
		setCounter(c => c + 1);
	}, [])

	return <h1>{counter}</h1>;
}

Why this works:

  • React owns the state
  • Updates are predictable
  • Even with double execution → no chaos

💡 Important Detail:

typescript

setCounter(c => c + 1);

This is a functional update

👉 It ensures correctness even with multiple renders.

🚨 Another Common Real-World Bug

Blog image

Now let’s move to a very practical example:

typescript

useEffect(() => {
	window.addEventListener("click", () => {
	console.log("Clicked");
	  })
}, [])

Output:

plain text

Clicked
Clicked

What happened?

React mounted the component twice.

So:

👉 Two event listeners got attached.

This is not React’s fault.

This is your missing cleanup.

🧹 The Fix

Blog image

Whenever you create something inside useEffect:

👉 You must clean it up.

Fixed version:

typescript

useEffect(() => {
	consthandleClick= () => {
		console.log("Clicked");
	}
	
	window.addEventListener("click", handleClick);
	
	return () => {
		window.removeEventListener("click",handleClick);
	}
}, [])

What happens now:

plain text

Mount → Add listener
Unmount → Remove listener
Mount again → Add listener

Only ONE listener exists.

🧠 Deep Insight (Senior-Level Thinking)

Every useEffect has a lifecycle:

plain text

Effect runs → Cleanup runs → Effect runs again

So always think:

👉 “If this runs twice, will it break?”

If yes…

👉 Your effect is unsafe.

📦 Common Things That Require Cleanup

You MUST clean up:

  • Event listeners
  • Timers (setTimeout, setInterval)
  • API subscriptions
  • WebSockets
  • Observers

🚫 What Beginners Usually Do

  • Ignore double execution
  • Disable Strict Mode ❌
  • Add hacks ❌

✅ What Professionals Do

  • Understand the root cause
  • Fix side effects
  • Write predictable components

🧩 The Bigger Picture

Blog image

Think of Strict Mode as:

👉 A free testing layer from React

It helps you detect:

  • Memory leaks
  • Duplicate subscriptions
  • Uncontrolled mutations
  • Unpredictable state

🧠 Final Mental Shift

Don’t think:

❌ “Why is React doing this?”

Start thinking:

✅ “What is React trying to protect me from?”

🎯 Final Takeaways

  • useEffect running twice is intentional
  • It happens only in development
  • It helps detect:
  • Hidden side effects
  • Missing cleanup
  • Unsafe logic

🔥 One-Line Summary

👉 If your code breaks when run twice, it was already broken.

🚀 What You Just Learned

Blog image

You didn’t just fix a bug.

You understood:

  • How React validates your code
  • Why purity matters
  • How effects actually work

🔜 Next Post

Why React components re-render (and why most developers misunderstand it)

Post details

Published

Apr 3, 2026

Updated

Mar 31, 2026

Read time

7 min read

Tags

ReactTechnicalTutorialBest PracticesGuide

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→
SetState feels broken
ReactApr 15, 2026
SetState feels brokenTechnical

SetState feels broken

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

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→