React  

How to Fix React useEffect Running Multiple Times?

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.