Twice execution of useEffect in React :👈 👉:State-Not-Updating-Immediately

How to Fix Maximum Update Depth Exceeded Error

How to Fix React Maximum Update Depth Exceeded

1. Quick Answer

The "Maximum update depth exceeded" error occurs when a React component repeatedly triggers state updates, causing an infinite update loop.

React detects the repeated updates and stops the process to prevent the application from getting stuck.

Error message:

Maximum update depth exceeded.
This can happen when a component repeatedly calls setState
inside componentWillUpdate or componentDidUpdate.
React limits the number of nested updates to prevent infinite loops.

The exact error message may vary depending on the React version and the code causing the problem.

Common Cause 1: Updating State Inside useEffect Without Proper Dependencies

Incorrect code:

import { useEffect, useState } from "react";
function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        setCount(count + 1);
    });
    return <p>Count: {count}</p>;
}

Problem: The Effect runs after every committed render and updates the state. The update causes another render, which triggers the Effect again.

Correct code when the intention is to initialize the count to 1:

import { useEffect, useState } from "react";
function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        setCount(1);
    }, []);
    return <p>Count: {count}</p>;
}

The empty dependency array prevents the Effect from running again merely because the component re-renders. In development Strict Mode, React may still perform an additional Effect setup and cleanup cycle.

Important: Adding an empty dependency array is not a universal fix. The correct dependencies depend on what the Effect needs to synchronize.

Common Cause 2: Calling an Event Handler During Rendering

Incorrect code:

<button onClick={handleClick()}>
    Increment
</button>

Correct code:

<button onClick={handleClick}>
    Increment
</button>

Passing handleClick allows React to execute the function when the user clicks the button. Calling handleClick() executes it immediately during rendering.

2. Table of Contents

  1. Concept Explanation
  2. Diagram
  3. Real-World Example
  4. Code Examples and Fixes
  5. Common Mistakes
  6. When to Use It
  7. When NOT to Use It
  8. Interview Questions

3. Concept Explanation

3.1 What Is Maximum Update Depth Exceeded?

React components can update their state and render again. This is normal behavior.

However, when a state update repeatedly triggers another update without reaching a stable state, React may detect an infinite update loop and throw the "Maximum update depth exceeded" error.

For example:

Render
  ↓
State Update
  ↓
Render Again
  ↓
State Update
  ↓
Render Again
  ↓
Repeated Updates
  ↓
React Throws an Error

This error is related to repeated updates, whereas the "Too many re-renders" error commonly indicates that a component is updating state during its own rendering process.

3.2 How Does React State Updating Work?

Consider the following code:

const [count, setCount] = useState(0);

Here:

  • count stores the current state value.
  • setCount requests a state update.
  • useState(0) initializes the state to zero.

When you execute setCount(count + 1), React schedules an update and may render the component again.

If every render or Effect execution unconditionally requests another update, the component may enter a loop.

3.3 Maximum Update Depth vs. Too Many Re-renders

Aspect Too Many Re-renders Maximum Update Depth Exceeded
Common cause Unconditional state updates during rendering. Repeated nested updates, often caused by an Effect, lifecycle method, or callback.
Typical example Calling setCount(count + 1) directly in the component body. An Effect repeatedly updates state, or a callback repeatedly triggers updates.
When detected During repeated render attempts. When React detects an excessive update cycle.
Solution Move updates out of unconditional rendering logic. Break the repeated update cycle and correct dependencies or callback logic.

These errors are related and can arise from similar mistakes. Their exact detection and wording can vary with the React version and update path.

3.4 Common Causes

  • Updating state inside an Effect that runs after every render.
  • Using an Effect whose state update changes one of its dependencies.
  • Calling an event handler instead of passing the function reference.
  • Creating a new object or function on every render and using it as an Effect dependency.
  • Updating a parent component's state repeatedly from a child component.
  • Updating state inside a callback that is triggered again by the resulting render.
  • Using class component lifecycle methods to repeatedly update state without a valid stopping condition.

4. Diagram

4.1 How the Error Occurs

Component Renders Effect or Callback Runs State Updates Component Renders Again Effect or Callback Runs State Updates Repeated Update Loop Maximum Update Depth Error Effect runs again after render State update triggers another render React stops infinite updates

4.2 Correct Update Flow

Component Renders Wait for User Action User Clicks Button Event Handler Executes State Updates Component Renders Again UI Displays New State Stable State

The key is to ensure that a state update does not automatically trigger the same update again without a valid reason.

5. Real-World Example

Scenario: Updating an Employee Dashboard

Imagine an employee dashboard that displays the number of pending tasks. The application needs to update the count when the user clicks a button.

Incorrect Implementation

import { useEffect, useState } from "react";
function EmployeeDashboard() {
    const [pendingTasks, setPendingTasks] = useState(5);
    useEffect(() => {
        setPendingTasks(pendingTasks + 1);
    }, [pendingTasks]);
    return (
        <div>
            <h2>Employee Dashboard</h2>
            <p>Pending Tasks: {pendingTasks}</p>
        </div>
    );
}
export default EmployeeDashboard;

What happens?

  1. The component renders with five pending tasks.
  2. The Effect runs and increases the count to six.
  3. The state changes, so React renders again.
  4. The dependency pendingTasks has changed.
  5. The Effect runs again and increases the count to seven.
  6. The cycle continues until React stops the repeated updates.

Correct Implementation

import { useState } from "react";
function EmployeeDashboard() {
    const [pendingTasks, setPendingTasks] = useState(5);
    function addTask() {
        setPendingTasks(previousCount => previousCount + 1);
    }
    return (
        <div>
            <h2>Employee Dashboard</h2>
            <p>Pending Tasks: {pendingTasks}</p>
            <button onClick={addTask}>
                Add Task
            </button>
        </div>
    );
}
export default EmployeeDashboard;

Why does this work?

  • The initial count is five.
  • No state update occurs automatically during rendering.
  • The state changes only when the user clicks the button.
  • The functional updater calculates the next value from the previous state.
  • React renders the updated count and waits for the next user action.

6. Code Examples and Fixes

6.1 Fix an Effect That Updates State After Every Render

Incorrect:

useEffect(() => {
    setCount(count + 1);
});

This Effect runs after every committed render. Because it updates the state, it can trigger another render and another Effect execution.

Fix: First determine why the state needs to change. If the intention is to initialize the value to one, use the initial state directly:

const [count, setCount] = useState(1);

If an Effect must synchronize state with an external system, use the correct dependencies and ensure the synchronization does not repeatedly trigger itself.

6.2 Fix an Effect That Updates Its Own Dependency

Incorrect:

useEffect(() => {
    setCount(count + 1);
}, [count]);

Why is it wrong?

The Effect updates count, which is also a dependency. The updated value triggers the Effect again.

Possible fix when the intended value is a constant initial value:

const [count, setCount] = useState(1);

If the value must be updated by a user, use an event handler instead:

function increment() {
    setCount(previousCount => previousCount + 1);
}

Do not remove count from the dependency array simply to suppress the loop. Instead, redesign the logic so the Effect reflects the intended synchronization behavior.

6.3 Fix an Event Handler That Executes During Rendering

Incorrect:

function Counter() {
    const [count, setCount] = useState(0);
    function increment() {
        setCount(count + 1);
    }
    return (
        <button onClick={increment()}>
            Increment
        </button>
    );
}

Correct:

function Counter() {
    const [count, setCount] = useState(0);
    function increment() {
        setCount(count + 1);
    }
    return (
        <button onClick={increment}>
            Increment
        </button>
    );
}

React now calls the function in response to the click event rather than during rendering.

6.4 Fix an Effect That Depends on a New Object Every Render

Incorrect:

function UserProfile({ userId }) {
    const options = { userId: userId };
    useEffect(() => {
        console.log("Loading user", options.userId);
    }, [options]);
    return <p>User ID: {userId}</p>;
}

Problem: The options object is recreated during each render. Its reference changes, so React treats the Effect dependency as changed.

This alone does not necessarily cause an infinite loop because the Effect does not update state. However, if the Effect updates state, it can contribute to a repeated update cycle.

Correct:

function UserProfile({ userId }) {
    useEffect(() => {
        const options = { userId: userId };
        console.log("Loading user", options.userId);
    }, [userId]);
    return <p>User ID: {userId}</p>;
}

Now the Effect depends on the actual primitive value it needs rather than a newly created object.

6.5 Fix a Parent-Child State Update Loop

A child component may call a parent state update function during rendering, causing the parent and child to render repeatedly.

Incorrect:

function Child({ setMessage }) {
    setMessage("Updated");
    return <p>Child Component</p>;
}
function Parent() {
    const [message, setMessage] = useState("Initial");
    return (
        <div>
            <p>{message}</p>
            <Child setMessage={setMessage} />
        </div>
    );
}

Problem: The child updates the parent's state during rendering. The parent renders again, which renders the child again, causing another update.

Correct: Update the parent state in response to an event.

function Child({ onUpdate }) {
    return (
        <button onClick={onUpdate}>
            Update Message
        </button>
    );
}
function Parent() {
    const [message, setMessage] = useState("Initial");
    function updateMessage() {
        setMessage("Updated");
    }
    return (
        <div>
            <p>{message}</p>
            <Child onUpdate={updateMessage} />
        </div>
    );
}

The parent state now changes only when the user clicks the button.

6.6 Fix an Effect That Creates a Timer Repeatedly

Incorrect:

useEffect(() => {
    setInterval(() => {
        setCount(count + 1);
    }, 1000);
}, [count]);

Problem: Each time count changes, the Effect creates another interval. Without cleanup, earlier intervals remain active.

Correct:

useEffect(() => {
    const timerId = setInterval(() => {
        setCount(previousCount => previousCount + 1);
    }, 1000);
    return () => {
        clearInterval(timerId);
    };
}, []);

The functional state update uses the latest previous value without requiring count as a dependency. The cleanup function clears the interval when the Effect is cleaned up.

6.7 Use Functional State Updates Correctly

When the next state depends on the previous state, use the functional updater form:

setCount(previousCount => previousCount + 1);

This avoids relying on a potentially outdated state value captured by a closure.

However, remember that a functional updater does not fix an infinite loop if the state setter is still called unconditionally during rendering or by an Effect that continually re-triggers itself.

6.8 Avoid Updating State When the Value Has Not Changed

If an update is genuinely necessary, check whether the next value differs from the current value before requesting an update.

setStatus(previousStatus => {
    if (previousStatus === "Completed") {
        return previousStatus;
    }
    return "Completed";
});

React can skip rendering work when the next state is identical to the previous state according to Object.is. However, this should not be used as a substitute for correcting an unconditional update loop.

7. Common Mistakes

Mistake Problem Solution
Calling a state setter during rendering Repeatedly triggers rendering. Move the update to an event handler or use a justified conditional state adjustment.
Using useEffect without a dependency array An Effect that updates state can run after every render. Correct the Effect logic and specify appropriate dependencies.
Updating a dependency inside the same Effect Can cause the Effect to trigger itself repeatedly. Redesign the synchronization logic or move event-driven updates into handlers.
Calling an event handler during rendering The handler executes before a user interaction occurs. Pass the function reference to the event handler property.
Creating timers without cleanup Multiple timers can remain active. Clear timers in the cleanup function.
Creating objects or functions as Effect dependencies on every render Can cause the Effect to re-run unnecessarily. Use the actual dependencies and restructure the Effect where appropriate.
Updating parent state inside a child component's rendering logic Can trigger repeated parent-child updates. Update parent state in an event handler or another appropriate synchronization flow.
Adding an empty dependency array to every Effect Can cause stale data or incorrect synchronization. Choose dependencies based on the Effect's actual requirements.
Using useCallback or useMemo as a universal fix Memoization does not fix every update loop. Identify and correct the underlying update cycle.

Debugging Checklist

  1. Read the error stack trace and identify the component involved.
  2. Search for calls to state setters such as setCount and setMessage.
  3. Check whether a setter executes during rendering.
  4. Inspect all Effects that update state.
  5. Check whether an Effect updates one of its own dependencies.
  6. Look for functions or objects that receive new references on every render.
  7. Check parent-child communication for repeated updates.
  8. Verify that timers and subscriptions are cleaned up correctly.
  9. Test the application again and confirm that the component reaches a stable state.

8. When to Use It

The "Maximum update depth exceeded" error is not a feature to use intentionally. Understanding it is useful when:

  • Debugging React applications: Identifying the source of repeated state updates.
  • Working with useEffect: Correcting Effects that update their own dependencies.
  • Handling forms: Preventing validation and state synchronization from repeatedly triggering one another.
  • Managing parent-child communication: Avoiding repeated updates between components.
  • Implementing timers and subscriptions: Ensuring that setup and cleanup are handled correctly.
  • Improving application stability: Preventing update loops that interrupt normal application behavior.

Recommended Practices

  • Keep rendering logic pure.
  • Use event handlers for user-triggered state updates.
  • Use Effects only when synchronization with an external system is needed.
  • Declare the correct dependencies for each Effect.
  • Use functional state updates when the next value depends on the previous state.
  • Clean up timers, subscriptions, and event listeners.
  • Ensure conditional update logic has a valid stopping condition.

9. When NOT to Use It

Do not intentionally create repeated update loops in a production application.

Also, avoid these approaches when trying to fix the error:

  • Do not remove all state updates: Interactive applications require state changes.
  • Do not move every state update into useEffect: User-triggered changes generally belong in event handlers.
  • Do not disable Strict Mode to hide the error: Investigate and fix the underlying problem.
  • Do not remove Effect dependencies arbitrarily: This can create stale values and incorrect synchronization.
  • Do not add arbitrary timeouts: Delaying an update does not necessarily break the repeated update cycle.
  • Do not rely solely on useCallback or useMemo: Memoization does not replace correct state-management logic.
  • Do not suppress the error without understanding its cause: The same bug may continue to affect application behavior.

Important distinction: Normal re-rendering is expected in React. The objective is to prevent an uncontrolled update loop, not to prevent every render.

10. Interview Questions

Q1. What does "Maximum update depth exceeded" mean in React?

Answer: It means React has detected an excessive cycle of nested updates, commonly caused by a component or Effect repeatedly updating state without reaching a stable condition.

Q2. What is the most common cause of this error in functional components?

Answer: A common cause is an Effect that updates state and then runs again because its dependencies change. Unconditional state updates during rendering can produce a related error.

Q3. How do you fix an infinite loop caused by useEffect?

Answer: Identify the state update that triggers the Effect again. Remove unnecessary Effects, use appropriate dependencies, or move user-triggered updates into event handlers.

Q4. Why is useEffect without a dependency array potentially dangerous?

Answer: The Effect runs after every committed render. If it unconditionally updates state, it can trigger another render and execute repeatedly.

Q5. What happens when an Effect updates one of its own dependencies?

Answer: The state update can change the dependency, causing React to run the Effect again. If the Effect repeats the same update pattern without a stopping condition, an infinite update loop can occur.

Q6. What is the difference between Maximum Update Depth Exceeded and Too Many Re-renders?

Answer: Both indicate problematic update cycles. "Too many re-renders" commonly identifies repeated updates during rendering, while "Maximum update depth exceeded" commonly identifies an excessive sequence of nested updates. The exact error depends on the update path and React version.

Q7. Why should event handlers be passed as function references?

Answer: Passing a function reference allows React to execute it in response to an event. Calling the function during rendering may execute its state updates immediately and trigger repeated rendering.

Q8. How do you prevent repeated interval creation inside useEffect?

Answer: Create the interval inside the Effect and return a cleanup function that calls clearInterval. Use a functional state update when the interval needs to increment the current state.

Q9. Can useCallback solve Maximum Update Depth Exceeded?

Answer: Sometimes memoization can help when a changing function reference unnecessarily triggers an Effect. However, useCallback does not fix an unconditional state update or an incorrectly designed Effect.

Q10. Why is a functional state update useful?

Answer: It calculates the next state from the previous state and avoids relying on a potentially outdated value captured by a closure.

setCount(previousCount => previousCount + 1);

Q11. Can updating parent state from a child cause an infinite loop?

Answer: Yes. If a child updates parent state during rendering, the parent may render again and render the child again, repeating the update. Move the update to an event handler or use another appropriate state-management pattern.

Q12. What role does React Strict Mode play in debugging update loops?

Answer: Strict Mode enables additional development checks that can expose unsafe rendering logic and missing Effect cleanup. It may reveal a bug but does not make an unconditional state-update loop correct.

Q13. How do you determine whether an Effect dependency is causing repeated execution?

Answer: Inspect the dependency array and identify which values change after each render. Objects and functions created during rendering often receive new references. Restructure the Effect to depend on the actual reactive values it needs.

Q14. Is it safe to remove a dependency from useEffect to stop the loop?

Answer: Not necessarily. Removing a required dependency can cause stale values and incorrect synchronization. Instead, redesign the Effect or update logic so the declared dependencies correctly represent its requirements.

Q15. What is the best way to prevent Maximum Update Depth Exceeded?

Answer: Keep rendering pure, use event handlers for user-triggered updates, write Effects with correct dependencies and cleanup, avoid redundant state, and ensure that every conditional update eventually reaches a stable state.

Summary

  • The error indicates an excessive or repeated update cycle.
  • Check Effects that update state, especially those without dependencies or with dependencies they repeatedly change.
  • Do not execute event handlers during rendering.
  • Clean up timers, event listeners, and subscriptions.
  • Use functional state updates when the next value depends on the previous state.
  • Do not remove dependencies or disable Strict Mode simply to hide the error.
  • Fix the underlying update logic so React can reach a stable state.
Back to Index
Twice execution of useEffect in React :👈 👉:State-Not-Updating-Immediately