Introduction
If you are learning React or building your first real-world project, you may notice that useEffect runs multiple times, even when you expect it to run only once. This behavior can be confusing and may create unexpected bugs such as duplicate API calls, repeated logs, or flashing UI components. In this article, we will explain, in simple terms, why useEffect runs more than once, the main causes, and provide clear step-by-step solutions to fix the issue. Whether you are using React for the web or React Native, these solutions will help you write cleaner and more efficient code.
Why Does useEffect Run Multiple Times?
The useEffect hook runs when a component renders. But depending on your code, it may run again because of:
React Strict Mode
Missing or incorrect dependency arrays
State changes happening inside useEffect
Component re-renders caused by parent components
API calls or listeners are not cleaned up properly
Let’s understand each cause and solution.
1. React Strict Mode (Runs useEffect Twice in Development)
React 18 introduced a behavior where useEffect runs twice in development mode when Strict Mode is enabled. This is done intentionally to help developers find bugs.
How to Identify the Issue
If useEffect is running twice only in development and not in production, Strict Mode is the reason.
Example
useEffect(() => {
console.log("Effect executed");
}, []);
You will see the log twice in development, but once in production.
How to Fix
You cannot disable this behavior, but you can remove Strict Mode in main.jsx or index.js:
// Remove <React.StrictMode>
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
However, it is not recommended, because Strict Mode helps catch hidden bugs.
2. Missing Dependency Array Causes Repeated Execution
If you do not provide a dependency array, useEffect runs after every render.
Example
useEffect(() => {
console.log("Running every time");
});
This effect runs on every state or prop change.
How to Fix
Add an empty dependency array if you want useEffect to run only once.
useEffect(() => {
console.log("Runs once after initial render");
}, []);
3. Incorrect Dependency Array Leading to Multiple Renders
If you include variables in the dependency array that change frequently, useEffect will re-run.
Example
useEffect(() => {
fetchData();
}, [count]); // will run every time count changes
How to Fix
Only include variables that are necessary.
If you want to run the effect once, remove unnecessary dependencies.
useEffect(() => {
fetchData();
}, []);
If you must include a function, wrap it in useCallback:
const fetchData = useCallback(() => {
// API call
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
4. State Updates Inside useEffect Trigger Re-Renders
If you update state without conditions inside useEffect, it can cause a loop.
Example of Infinite Loop
useEffect(() => {
setCount(count + 1); // re-renders again and again
}, []);
Although the dependency array is empty, React still re-renders after state updates.
How to Fix
Wrap state updates with conditions.
useEffect(() => {
if (count === 0) {
setCount(1);
}
}, [count]);
Or avoid unnecessary state updates.
5. Parent Component Re-Rendering
Your component's useEffect may run again because the parent component re-renders, which triggers re-rendering of children.
Example
A parent changes state:
function Parent() {
const [value, setValue] = useState(0);
return <Child />;
}
Child's useEffect will re-run.
How to Fix
Use React.memo to prevent unnecessary child renders.
Move logic and state to proper components.
Use context or global state carefully.
const Child = React.memo(() => {
useEffect(() => {
console.log("Child ran");
}, []);
return <div>Child</div>;
});
6. API Calls or Event Listeners Not Cleaned Up
If your useEffect returns nothing or forgets to clean up listeners, it can cause multiple executions.
Example
useEffect(() => {
window.addEventListener("scroll", handleScroll);
});
This adds a new listener on every render.
How to Fix
Always clean up:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
7. Async Functions Causing Duplicate Behavior
Using async directly inside useEffect can create confusion.
Wrong Approach
useEffect(async () => {
await fetchData();
}, []);
Correct Approach
Always define an async function inside:
useEffect(() => {
const loadData = async () => {
await fetchData();
};
loadData();
}, []);
Best Practices to Prevent useEffect from Running Multiple Times
Use an empty dependency array when needed.
Use React.memo for child components.
Wrap functions in useCallback.
Wrap objects/arrays in useMemo.
Clean up event listeners.
Avoid unnecessary state updates.
Understand Strict Mode behavior in React 18.
Conclusion
React useEffect may run multiple times due to dependency arrays, re-renders, Strict Mode, or improper cleanup. Once you understand why it happens, fixing the issue becomes simple and predictable. By following best practices like using correct dependencies, handling state updates carefully, and cleaning up listeners, you can ensure your React components behave smoothly without unexpected re-renders or duplicate calls. With the right approach, useEffect becomes a powerful and reliable tool in your React development journey.