Skip to content

fix: throw error on useEffect infinite loops in production#36443

Open
harshagarwalnyu wants to merge 3 commits intofacebook:mainfrom
harshagarwalnyu:fix-infinite-loop-silent-prod
Open

fix: throw error on useEffect infinite loops in production#36443
harshagarwalnyu wants to merge 3 commits intofacebook:mainfrom
harshagarwalnyu:fix-infinite-loop-silent-prod

Conversation

@harshagarwalnyu
Copy link
Copy Markdown

Fixes #36423

Currently, infinite render loop detection for passive effects (useEffect) is entirely wrapped in an if (DEV) block. In production builds, this check is stripped, causing infinite loops to run silently until the browser tab hangs or crashes. This prevents error boundaries from catching the issue and hides it from error monitoring tools.

This PR promotes the
estedPassiveUpdateCount check out of DEV and changes the console.error to an unconditional hrow new Error, matching the behavior of synchronous infinite loop detection.

Copilot AI review requested due to automatic review settings May 9, 2026 20:46
@meta-cla
Copy link
Copy Markdown

meta-cla Bot commented May 9, 2026

Hi @harshagarwalnyu!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to ensure useEffect (passive effect) infinite update loops throw an error in production builds, so error boundaries and monitoring can catch them (aligning behavior with the existing synchronous nested update guard).

Changes:

  • Adds a nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT check that throws an error (instead of only logging in __DEV__).
  • Resets nestedPassiveUpdateCount/rootWithPassiveNestedUpdates when the limit is exceeded.
  • Modifies logic inside doubleInvokeEffectsInDEVIfNecessary (StrictEffects DEV path), but the current hunk appears to introduce a syntax/control-flow break and places the check in a DEV-only codepath.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 5323 to 5336
const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;

// First case: the fiber **is not** of type OffscreenComponent. No
// special rules apply to double invoking effects.
if (fiber.tag !== OffscreenComponent) {
if (fiber.flags & PlacementDEV) {
if (isInStrictMode) {
runWithFiberInDEV(fiber, doubleInvokeEffectsOnFiber, root, fiber);
}
} else {
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
root,
fiber,
isInStrictMode,
);
}
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
nestedPassiveUpdateCount = 0;
rootWithPassiveNestedUpdates = null;

throw new Error(
"Maximum update depth exceeded. This can happen when a component " +
"calls setState inside useEffect, but useEffect either doesn't " +
"have a dependency array, or one of the dependencies changes on " +
"every render.",
);
}
return;
}
Comment on lines 5324 to 5337
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
nestedPassiveUpdateCount = 0;
rootWithPassiveNestedUpdates = null;

throw new Error(
"Maximum update depth exceeded. This can happen when a component " +
"calls setState inside useEffect, but useEffect either doesn't " +
"have a dependency array, or one of the dependencies changes on " +
"every render.",
);
}
return;
}

@harshagarwalnyu harshagarwalnyu force-pushed the fix-infinite-loop-silent-prod branch from 4e37f8b to a24bbda Compare May 9, 2026 22:32
@meta-cla meta-cla Bot added the CLA Signed label May 9, 2026
@harshagarwalnyu
Copy link
Copy Markdown
Author

Thanks for the review @copilot! Both issues addressed in the latest push:

  1. Wrong call site: Moved the
    estedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT\ check into \ hrowIfInfiniteUpdateLoopDetected\ directly — outside any _DEV_\ guard — so it fires in production builds. Changed \console.error\ to \ hrow new Error\ to match the existing synchronous loop detection behavior.

  2. **Broken control flow in \doubleInvokeEffectsInDEVIfNecessary**: Restored the function to its original upstream structure — re-added the missing \isInStrictMode\ assignment, the \ iber.tag !== OffscreenComponent\ guard, and the correct
    eturn;\ placement. The accidental duplicate throw and orphaned brace are gone.

@matisaar
Copy link
Copy Markdown

Hi team and community,

I wanted to share a financial dataset I've been compiling that might be useful for testing, benchmarking, or examples in this project:

DataForge Financial Data Bundle

  • 3,518 real records across 5 datasets
  • Crypto, AI stocks, EV stocks, Tech OHLCV, Macro indicators
  • CSV format (pandas-compatible)

Free sample: https://matisaar.github.io/dataforge-sales/DATAFORGE_FREE_SAMPLE.zip
Full bundle ($49): https://matisaar.github.io/dataforge-sales/

If anyone finds it useful for testing dataframes, visualization, or ML pipelines, I'd love feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: useEffect infinite loops are silent in production - NESTED_PASSIVE_UPDATE_LIMIT check is __DEV__-only and never throws

3 participants