LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #7: The 'Double' Nightmare – When React StrictMode tests your patience
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #7: The 'Double' Nightmare – When React StrictMode tests your patience

Why does the log appear twice? Why is the API called twice? Why is my state doubling? Welcome to the world of StrictMode.

TP
Truong PhamSoftware Engineer
PublishedMarch 28, 2024

There is a classic question on every React forum: "Why does my useEffect run twice?".

I was the one asking that question a few years ago when I first started getting familiar with React 18. At the time, I was building an Analytics feature: every time a User enters a page, I send a "view_page" request to the server to count views. Checking the Network Tab, I saw 2 identical requests sent within just a few milliseconds.

My initial reaction? "Must be a React bug!". I angrily looked for a way to disable StrictMode in the main.tsx file. And everything went back to "normal": only 1 request was sent. I breathed a sigh of relief and told myself: "This StrictMode is useless, just messing up my app."

But that was my biggest mistake. Disabling StrictMode isn't fixing the bug; it's like covering your ears when an alarm is going off.

1. The "Enemy" called Idempotency

Why does React intentionally run your useEffect, constructor, or render function twice in a Development environment?

React's goal is noble: it forces you to write "clean" code. A proper component must be Idempotent. This means if you run it 1 or 10 times with the same input, the result (or side effect) must not break the system.

In the future, React might mount/unmount your components continuously to optimize performance (like the Offscreen/Activity feature). If your code "dies" when run twice now, it will definitely crash on Production in the future.

2. The Problem: When State "doubles itself"

The most serious error I encountered was when writing shopping cart update logic. I wrote a piece of code that changed state based on the old value in an... unsafe way.

// Dangerous code
useEffect(() => {
  // Suppose this is logic to add an item to the cart
  setCart(prev => [...prev, newItem]); 
  // In Dev mode, the result is that the cart has 2 identical items!
  // I panicked, I thought the data fetching code was wrong.
}, []);

In reality, React StrictMode detected that I was using a side-effect (in a more complex real-world example, a global variable or external mutation) without cleanup. It "reminded" me: "Hey, if I run this twice and your data is wrong, it means your logic is unsafe!".

3. The Process: From "Turn it off" to "Thorough Cleanup"

After being "slapped" by the reality that turning off StrictMode caused my Analytics to double-count views in the real environment (due to users hitting Back/Forward quickly), I started learning to love StrictMode again.

I realized the key lies in the Cleanup function of useEffect. Good logic must always be cleaned up when the component unmounts.

Fix for Analytics:

useEffect(() => {
  let isMounted = true;
  
  const trackView = async () => {
    await sendAnalytics();
    if (!isMounted) return; // If unmounted, don't process further
  };

  trackView();

  return () => {
    isMounted = false; // Cleanup!
  };
}, []);

Or more simply, use libraries like React Query to automatically handle deduplication.

4. Counter-argument: Is React making it hard for us?

Many will say: "This is too complicated, it wasn't this exhausting with Class Components." True, it is complicated. Forcing devs to think about cleanup right from the start increases complexity for beginners.

But looking at the big picture, this is the only way to build large-scale, resilient apps ready for React's powerful Concurrent Rendering features. If you find StrictMode annoying, you might be trying to work against the framework instead of with it.

5. Junior vs. Senior: Persistence and Architecture

A Junior developer will spend 2 hours searching Google: "How to stop React from rendering twice." The final solution is usually removing the <StrictMode> tag.

A Senior developer will spend those 2 hours reviewing their logic. They will ask: "Why does rendering twice break this logic?". They see StrictMode as a free automated testing tool. If the app passes StrictMode, it's healthy enough to run in any server-side rendering or streaming environment.

Sustainable solutions:

  • Never remove StrictMode.
  • Always check if useEffect needs a return function for cleanup (clear timeout, remove event, abort fetch).
  • Use Refs to store state if you really need to know if the component has run the first time (but minimize this).

6. Lessons Learned

StrictMode isn't an error. It's a necessary "strictness."

  • If your code runs twice and produces the wrong result: your code is wrong, not React.
  • Get in the habit of writing cleanup functions for every side-effect.
  • Learn about Concurrent Mode to understand why React needs this "idempotency."

Final advice: Next time you see console logs appearing twice, don't get angry. Smile and thank React for helping you catch a potential bug before it gets to Production.


In React, 1 + 1 must always equal 1 if it's the result of the same action.

Series • Part 7 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #8: Memory Trash – The price of forgetting to 'clean up' your Event Listeners
Blog #6: Why you shouldn't fully trust console.log – The silent deceiver
02Blog #2: The 60 FPS Illusion – When your MacBook Pro 'deceives' your senses03Blog #3: Infinite Loops – When useEffect turns the browser into a 'furnace'04Blog #4: Hydration Error – When SSR's perfection 'slaps' me in the face at midnight05Blog #5: 200 OK – The system's sweet little lies06Blog #6: Why you shouldn't fully trust console.log – The silent deceiver07Blog #7: The 'Double' Nightmare – When React StrictMode tests your patienceReading08Blog #8: Memory Trash – The price of forgetting to 'clean up' your Event Listeners09Blog #9: Safari – The new 'Internet Explorer' of the modern era?10Blog #10: Specificity War – When !important marks the start of a civil war11Blog #11: Don't wait until 'heavy web' to lose weight for your Bundle12Blog #12: Don't bring the whole supermarket home just to buy a loaf of bread
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles