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.
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
useEffectneeds areturnfunction 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