-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn about async infinite useEffect loop #15180
Conversation
while (error === null) { | ||
Scheduler.unstable_flushNumberOfYields(1); | ||
await Promise.resolve(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my shoddy attempt to faithfully emulate deferred useEffect
case. (I don't want to flush it all synchronously.)
idontknowwhatimdoing.jpg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try this
ReactDOM.render(<App />, container, () => Scheduler.yieldValue('Did commit'));
expect(Scheduler).toFlushAndYieldThrough(['Did commit']);
// Synchronous effect has run, but not passive effects.
// Now flush the effects:
Scheduler.flushAll();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I forgot this is sync mode. Might be easier to use a boolean since the effect runs an arbitrary number of times before erroring.
ReactDOM.render(<App />, container);
expect(container.textContent).toEqual(whatever);
// Synchronous effect has run, but not passive effects.
expect(didFlushPassiveEffect).toBe(false);
// Now flush the effects:
Scheduler.flushAll();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to simulate a real deferred loop between them to make sure the warning still fires. Not sure if it matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To do that you need an assertion in between the synchronous commit phase and the effect phase. As is, your test would pass even if the passive effects were totally sync.
@@ -581,6 +584,14 @@ function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void { | |||
if (!isBatchingUpdates && !isRendering) { | |||
performSyncWork(); | |||
} | |||
|
|||
if (__DEV__) { | |||
if (rootWithPendingPassiveEffects === root) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only keep incrementing if flushing root's deferred effects schedules the same root again.
Details of bundled changes.Comparing: 3151813...9cdb8c4 react-dom
react-art
react-native-renderer
react-test-renderer
react-reconciler
Generated by 🚫 dangerJS |
const container = document.createElement('div'); | ||
ReactDOM.render(<App />, container); | ||
while (error === null) { | ||
Scheduler.unstable_flushNumberOfYields(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the first yieldValue
call happens in a passive effect, this actually will flush the effects synchronously
ReactDOM.render(<App />, container); | ||
while (error === null) { | ||
Scheduler.unstable_flushNumberOfYields(1); | ||
await Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this matters
This surfaces problems like #14920 (comment) where the same root keeps scheduling updates inside useEffect, and never manages to "rest". It only catches the "synchronous" case (setState directly in useEffect).
I've made it DEV-only because a crash would be a breaking change. The heuristic might also not be perfect yet and I'd like to give it some time to see if there are edge false positives.