As React applications become larger, finding bugs early becomes increasingly important.
Some bugs are difficult to notice during normal development because they may only appear under certain rendering conditions. Examples include components that accidentally contain side effects during rendering, Effects that do not clean themselves up correctly, or code that relies on assumptions about how many times a function will run.
React provides Strict Mode to help developers identify some of these problems during development.
Strict Mode is a development-time tool that enables additional checks and behaviors designed to expose bugs and unsafe patterns earlier.
It does not change the production behavior of your application in the same way.
A simple Strict Mode setup looks like this:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
const root = createRoot(
document.getElementById("root")
);
root.render(
<StrictMode>
<App />
</StrictMode>
);
The important idea is:
Strict Mode helps you discover problems in your React code while developing.
It is not a feature that users need to interact with.
What Is Strict Mode?
Strict Mode is a development-only feature in React that enables additional checks and intentionally exercises certain parts of your application to help reveal bugs.
You enable it with:
<StrictMode>
<App />
</StrictMode>
It is imported from React:
import { StrictMode } from "react";
Strict Mode does not render a visible element.
For example:
function App() {
return (
<StrictMode>
<Dashboard />
</StrictMode>
);
}
StrictMode does not create a <div> or other DOM element.
Instead, it tells React to apply additional development checks to the components inside it.
Why Does Strict Mode Exist?
React encourages components to behave predictably.
A component should ideally be safe to render multiple times, and Effects should correctly clean up whatever they set up.
For example, this component has a problem:
function UserList() {
users.push({
name: "Alex"
});
return <p>{users.length}</p>;
}
The component is modifying data during rendering.
That can create unpredictable behavior.
Strict Mode can help expose this kind of problem during development.
Using <StrictMode>
A common approach is to wrap the entire application:
import { StrictMode } from "react";
root.render(
<StrictMode>
<App />
</StrictMode>
);
This means the checks apply throughout the application tree beneath StrictMode.
You can also use Strict Mode around a specific part of an application:
function App() {
return (
<>
<Header />
<StrictMode>
<NewDashboard />
</StrictMode>
<Footer />
</>
);
}
Now the Strict Mode behavior applies to the NewDashboard subtree.
This can be useful when gradually introducing Strict Mode into an existing application.
Strict Mode Does Not Appear in the DOM
Consider:
<StrictMode>
<App />
</StrictMode>
You will not find:
<StrictMode>
in the browser’s DOM.
Strict Mode is a React development mechanism, not a DOM component.
Double Rendering in Development
One of the most noticeable behaviors of Strict Mode is that React may intentionally render certain components more than once during development.
For example:
function App() {
console.log("App rendered");
return <h1>Hello React</h1>;
}
With Strict Mode enabled in development, you may see:
App rendered
App rendered
This can initially look like a bug.
It is intentional.
React uses this behavior to help identify code that is not safe to run multiple times.
Does Strict Mode Render Twice in Production?
No.
The extra development behavior associated with Strict Mode is intended to help identify problems during development.
You should therefore not design your application’s logic around the assumption that a component will always render exactly twice.
The correct approach is to write components that remain correct even when React renders them more than once.
Why Does React Render Components More Than Once?
React rendering should be treated as a calculation.
For example:
function Product({ price, quantity }) {
const total = price * quantity;
return <p>Total: ${total}</p>;
}
Rendering this component multiple times is safe because the component does not modify external data.
It simply calculates:
price × quantity
and returns UI.
Now consider:
function Product({ product }) {
product.views++;
return <p>{product.views}</p>;
}
This modifies an object during rendering.
Repeated rendering can expose the problem.
The underlying lesson is:
Rendering should be pure.
Strict Mode and Pure Rendering
A React component should ideally behave like a pure function.
For the same inputs, it should produce the same result without modifying external systems.
For example:
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
This is predictable.
But:
function Greeting({ name }) {
localStorage.setItem("lastName", name);
return <h1>Hello, {name}</h1>;
}
performs a side effect during rendering.
That is not a good pattern.
If you need to synchronize with browser storage, use an appropriate Effect or event handler depending on what the application is trying to accomplish.
Strict Effects
Strict Mode also helps identify problems with Effects.
Consider:
useEffect(() => {
connectToServer();
return () => {
disconnectFromServer();
};
}, []);
In development with Strict Mode, React may perform an additional setup-and-cleanup cycle to verify that the Effect is correctly implemented.
Conceptually, React may exercise the Effect like this:
Setup
↓
Cleanup
↓
Setup
This is development behavior.
The purpose is to expose Effects that do not correctly clean up their resources.
Why Is This Useful?
Suppose you write:
useEffect(() => {
window.addEventListener(
"resize",
handleResize
);
}, []);
There is no cleanup.
If the Effect is exercised more than once during development, you can discover that multiple listeners may be registered.
The correct implementation is:
useEffect(() => {
window.addEventListener(
"resize",
handleResize
);
return () => {
window.removeEventListener(
"resize",
handleResize
);
};
}, []);
Now setup and cleanup are properly paired.
Effects Should Be Reversible
A useful way to think about an Effect is:
Setup
↓
Resource exists
↓
Cleanup
↓
Resource removed
For example:
useEffect(() => {
const timer = setInterval(() => {
console.log("Running");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
The setup creates a timer.
The cleanup removes it.
This makes the Effect resilient when React needs to restart synchronization.
Event Listener Example
Incorrect:
useEffect(() => {
window.addEventListener("online", handleOnline);
}, []);
Correct:
useEffect(() => {
window.addEventListener("online", handleOnline);
return () => {
window.removeEventListener(
"online",
handleOnline
);
};
}, []);
Subscription Example
Incorrect:
useEffect(() => {
const subscription = store.subscribe(handleChange);
}, []);
Correct:
useEffect(() => {
const subscription = store.subscribe(handleChange);
return () => {
subscription.unsubscribe();
};
}, []);
The cleanup ensures the subscription does not remain active unnecessarily.
Strict Mode and Network Requests
A common development experience is seeing a network request happen more than once while Strict Mode is enabled.
For example:
useEffect(() => {
fetch("/api/users");
}, []);
During development, Strict Mode can expose that the Effect starts a request but does not provide appropriate cleanup or cancellation.
For requests that can be cancelled, you can use AbortController:
useEffect(() => {
const controller = new AbortController();
async function loadUsers() {
try {
const response = await fetch(
"/api/users",
{
signal: controller.signal
}
);
if (!response.ok) {
throw new Error("Request failed");
}
const data = await response.json();
setUsers(data);
} catch (error) {
if (error.name !== "AbortError") {
setError(error);
}
}
}
loadUsers();
return () => {
controller.abort();
};
}, []);
Now the request can be cancelled when the Effect is cleaned up.
Important: Do Not Hide the Problem
A common reaction to duplicate development requests is trying to prevent the second request with a ref:
const hasRun = useRef(false);
useEffect(() => {
if (hasRun.current) {
return;
}
hasRun.current = true;
// Work
}, []);
This can hide the symptom without fixing the underlying Effect design.
If the Effect creates an external resource, make its setup and cleanup correct instead.
If the application genuinely needs request caching or deduplication, use an appropriate data-fetching architecture rather than treating Strict Mode’s development behavior as the problem.
Strict Mode and Component Purity
Strict Mode is particularly useful for identifying accidental mutations.
Consider:
function ProductList({ products }) {
products.sort((a, b) => a.price - b.price);
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name}
</li>
))}
</ul>
);
}
The problem is:
products.sort(...)
sort() mutates the array.
If products came from props or another source, this can cause unexpected behavior.
A safer approach is:
const sortedProducts = [...products].sort(
(a, b) => a.price - b.price
);
Or, in environments where it is supported:
const sortedProducts = products.toSorted(
(a, b) => a.price - b.price
);
The component can now calculate its output without mutating the original array.
Strict Mode and State Updater Functions
Strict Mode can also help reveal impure state updater functions.
Consider:
setItems(items => {
items.push(newItem);
return items;
});
This mutates the existing state array.
Instead:
setItems(items => [
...items,
newItem
]);
The new version creates a new array.
This is easier for React to reason about and follows React’s immutable state update pattern.
Strict Mode and Deprecated Patterns
Strict Mode has historically helped developers identify APIs and patterns that are unsafe or discouraged in modern React.
As React evolves, Strict Mode can gain additional development checks.
This means you should treat Strict Mode as an ongoing development aid rather than a fixed list of warnings that never changes.
If React reports a Strict Mode issue, check the current React documentation and understand what pattern is being flagged instead of simply disabling Strict Mode.
Strict Mode Best Practices
Keep Rendering Pure
Rendering should calculate UI.
Avoid:
function Component() {
database.save(data);
return <div>Saved</div>;
}
Instead, perform the operation in an event handler or Effect depending on what triggers it.
For example, user interaction might belong in an event handler:
function SaveButton({ data }) {
function handleSave() {
database.save(data);
}
return (
<button onClick={handleSave}>
Save
</button>
);
}
Always Clean Up External Resources
If an Effect creates something that needs to be removed, clean it up.
Examples include:
Event listeners
Timers
Subscriptions
WebSockets
Observers
Connections
Abortable requests
Third-party instances
For example:
useEffect(() => {
const observer = new ResizeObserver(handleResize);
observer.observe(element);
return () => {
observer.disconnect();
};
}, []);
Treat Development Double Rendering as a Test
Instead of thinking:
“Why is React rendering this twice?”
think:
“Is my component safe if React renders it again?”
This mindset leads to better React code.
Do Not Depend on Render Counts
Avoid application logic such as:
if (renderCount === 2) {
// Do something
}
Render frequency is an implementation detail, not a reliable business event.
Your application should respond to actual state, props, events, and external system changes.
Do Not Use Refs to Hide Strict Mode Problems
A ref should not be used simply to prevent an Effect from running during development.
For example:
const hasRun = useRef(false);
useEffect(() => {
if (hasRun.current) return;
hasRun.current = true;
// Hide the second execution
}, []);
This can prevent you from discovering an actual cleanup or synchronization problem.
Instead, make the Effect safe to start, clean up, and start again.
Use Strict Mode During Development
For new applications, keeping Strict Mode enabled during development is generally useful.
For example:
root.render(
<StrictMode>
<App />
</StrictMode>
);
This gives React an opportunity to expose problems earlier.
Fix Warnings Instead of Suppressing Them
When Strict Mode exposes an issue, investigate it.
Do not automatically:
- remove Strict Mode
- add a ref to suppress an Effect
- disable lint rules
- ignore cleanup
- add arbitrary delays
- add flags whose only purpose is to hide duplicate execution
The goal is to make the code correct.
Strict Mode with Custom Hooks
Strict Mode also applies to Custom Hooks because Custom Hooks execute as part of components.
For example:
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener(
"resize",
handleResize
);
return () => {
window.removeEventListener(
"resize",
handleResize
);
};
}, []);
return size;
}
A component using it:
function Dashboard() {
const { width, height } = useWindowSize();
return (
<p>
{width} × {height}
</p>
);
}
The Hook should be designed so that its Effects can safely be set up and cleaned up.
This is one reason production-quality Custom Hooks need careful cleanup.
Strict Mode with Context
Strict Mode also works with Context.
For example:
<StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>
The provider and its descendants participate in Strict Mode’s development checks.
This does not mean Context state should be reset every time Strict Mode performs its development checks.
The important thing is that the provider and its Effects are implemented correctly.
Strict Mode and Third-Party Libraries
Third-party libraries can sometimes reveal Strict Mode issues.
For example, suppose a component initializes a chart:
useEffect(() => {
const chart = new Chart(element, options);
return () => {
chart.destroy();
};
}, [options]);
This is much safer than:
useEffect(() => {
new Chart(element, options);
}, [options]);
The second version creates an external resource without cleaning it up.
Strict Mode can make this problem visible during development.
Working with Libraries That Are Not Strict-Mode Friendly
If an old third-party library behaves incorrectly under Strict Mode, first determine whether the library or your integration code needs updating.
Possible solutions include:
- upgrading the library
- following the library’s current React integration guidance
- isolating legacy code
- creating correct setup and cleanup boundaries
Disabling Strict Mode should generally not be the first response.
Strict Mode and Performance
Strict Mode’s additional development behavior can make an application appear slower during development.
For example, you may notice:
More console logs
More development renders
Extra Effect setup/cleanup checks
This is expected.
Do not judge production performance solely from a development build running under Strict Mode.
When investigating real performance problems, use production-oriented measurements and profiling tools.
Strict Mode vs Production Behavior
It is important to distinguish development diagnostics from production behavior.
| Behavior | Development with Strict Mode | Production |
|---|---|---|
| Additional purity checks | Yes | No |
| Development-only extra render checks | Yes | No |
| Extra Effect setup/cleanup cycle | Yes | No |
| Strict Mode UI element | No visible DOM element | No visible DOM element |
| Normal React rendering | Yes | Yes |
| Normal state updates | Yes | Yes |
| Normal DOM commits | Yes | Yes |
Strict Mode does not mean your users will see the application render twice in production.
Its purpose is to help you find problems while developing.
Understanding Strict Mode with an Example
Consider:
function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
};
}, []);
return <h2>Chat Room</h2>;
}
The Effect has a clear lifecycle:
Effect setup
↓
Connect
↓
Cleanup
↓
Disconnect
↓
Setup again if React needs to resynchronize
The important part is that setup and cleanup are symmetrical.
Now imagine:
useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);
There is no cleanup.
This can expose a resource-management problem.
Strict Mode is useful because it encourages you to fix the underlying lifecycle rather than assume an Effect only runs once.
A Practical Strict Mode Debugging Process
When Strict Mode exposes unexpected behavior, follow a structured process.
Identify What Runs More Than Once
Add temporary logging:
console.log("Rendering Component");
or:
useEffect(() => {
console.log("Effect setup");
return () => {
console.log("Effect cleanup");
};
}, []);
This helps distinguish rendering from Effect behavior.
Check Rendering for Side Effects
Look for:
Mutating props
Mutating external objects
Writing to storage
Network requests
Subscriptions
DOM manipulation
during rendering.
Check Every Effect for Cleanup
Ask:
What did this Effect create?
Then ask:
How is that resource removed?
For example:
addEventListener
↓
removeEventListener
setInterval
↓
clearInterval
subscribe
↓
unsubscribe
connect
↓
disconnect
Check Whether the Effect Is Needed
Sometimes the best fix is removing the Effect entirely.
For example:
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
This is unnecessary derived state.
Instead:
const fullName = `${firstName} ${lastName}`;
The simpler version avoids an unnecessary Effect.
Strict Mode and Modern React Development
Strict Mode is especially valuable as React applications adopt more advanced rendering capabilities.
React may perform rendering work in ways that require components to be resilient to rendering more than once or having work started and discarded.
This reinforces an important React principle:
Do not rely on a render happening exactly once.
Instead:
- keep rendering pure
- make Effects correctly synchronize with external systems
- provide cleanup
- avoid hidden side effects
- keep state updates predictable
This makes components more robust as React evolves.
Common Strict Mode Mistakes
Mistaking Development Behavior for a Production Bug
Seeing duplicate logs in development does not automatically mean users will see duplicate UI.
First determine what is actually happening.
Removing Strict Mode Immediately
If Strict Mode exposes a bug, removing Strict Mode may only hide the problem.
Investigate the underlying cause first.
Using a Ref to Force an Effect to Run Once
This can hide incorrect Effect cleanup or synchronization.
Prefer making the Effect safe to restart.
Putting Side Effects in Rendering
Rendering should calculate UI.
Move external operations into appropriate Effects or event handlers.
Forgetting Cleanup
Always consider the lifecycle of external resources.
Assuming useEffect(..., []) Means “Never Again”
An empty dependency array means the Effect has no reactive dependencies. In development Strict Mode, React may still exercise its setup and cleanup cycle more than once.
Therefore, do not use an empty dependency array as a guarantee that setup code will execute exactly once for the lifetime of your development session.
Strict Mode Cheat Sheet
<StrictMode>
↓
Development-only checks
↓
Helps find unsafe patterns
Rendering:
Render should be pure
↓
No side effects
↓
Safe to render again
Effects:
Setup
↓
External resource
↓
Cleanup
Development:
Strict Mode
↓
Extra development checks
↓
Problems become easier to detect
Best practices:
Keep rendering pure
Clean up Effects
Avoid hidden side effects
Do not depend on render counts
Do not hide Strict Mode with refs
Fix the underlying problem
Keep Strict Mode enabled during development
Key Takeaways
Strict Mode is a React development feature that helps identify potential problems in your application.
You enable it with:
<StrictMode>
<App />
</StrictMode>
Strict Mode does not create a visible DOM element.
One of its most noticeable behaviors is additional development-time rendering and Effect checks. These checks can make component functions or Effect setup code appear to run more than once during development.
This behavior is intentional.
The goal is to expose code that depends on assumptions such as:
"This component only renders once."
"This Effect only runs once."
"This external resource never needs cleanup."
React wants components to be resilient to repeated rendering and Effects to correctly manage their setup and cleanup.
For rendering, this means keeping components pure:
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
For Effects, it means pairing setup with cleanup:
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
};
}, []);
When Strict Mode reveals duplicate requests, event listeners, subscriptions, timers, or other unexpected behavior, do not immediately try to hide the extra execution.
Instead, investigate whether the component has:
- side effects during rendering
- missing cleanup
- unnecessary Effects
- incorrect dependencies
- mutations
- unsafe third-party integrations
- assumptions about render frequency
Strict Mode is best viewed as a development testing tool for React code quality.
If your components render purely and your Effects correctly synchronize and clean up external systems, Strict Mode becomes much less mysterious and much more useful.