Skip to content

Commit fad5102

Browse files
authored
[bugfix] Fix false positive render phase update (#16907)
Need to reset the current "debug phase" inside the catch block. Otherwise React thinks we're still in the render phase during the subsequent event.
1 parent a9cd9a7 commit fad5102

File tree

2 files changed

+57
-5
lines changed

2 files changed

+57
-5
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,7 @@ function handleError(root, thrownValue) {
13291329
// Reset module-level state that was set during the render phase.
13301330
resetContextDependencies();
13311331
resetHooks();
1332+
resetCurrentDebugFiberInDEV();
13321333

13331334
if (workInProgress === null || workInProgress.return === null) {
13341335
// Expected to be working on a non-root fiber. This is a fatal error
@@ -2672,10 +2673,12 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
26722673
throw originalError;
26732674
}
26742675

2675-
// Keep this code in sync with renderRoot; any changes here must have
2676+
// Keep this code in sync with handleError; any changes here must have
26762677
// corresponding changes there.
26772678
resetContextDependencies();
26782679
resetHooks();
2680+
// Don't reset current debug fiber, since we're about to work on the
2681+
// same fiber again.
26792682

26802683
// Unwind the failed stack frame
26812684
unwindInterruptedWork(unitOfWork);
@@ -3072,10 +3075,10 @@ function startWorkOnPendingInteractions(root, expirationTime) {
30723075
);
30733076

30743077
// Store the current set of interactions on the FiberRoot for a few reasons:
3075-
// We can re-use it in hot functions like renderRoot() without having to
3076-
// recalculate it. We will also use it in commitWork() to pass to any Profiler
3077-
// onRender() hooks. This also provides DevTools with a way to access it when
3078-
// the onCommitRoot() hook is called.
3078+
// We can re-use it in hot functions like performConcurrentWorkOnRoot()
3079+
// without having to recalculate it. We will also use it in commitWork() to
3080+
// pass to any Profiler onRender() hooks. This also provides DevTools with a
3081+
// way to access it when the onCommitRoot() hook is called.
30793082
root.memoizedInteractions = interactions;
30803083

30813084
if (interactions.size > 0) {

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,4 +2576,53 @@ describe('ReactSuspenseWithNoopRenderer', () => {
25762576
expect(root).toMatchRenderedOutput(<span prop="Initial" />);
25772577
});
25782578
});
2579+
2580+
it('regression test: resets current "debug phase" after suspending', async () => {
2581+
function App() {
2582+
return (
2583+
<Suspense fallback="Loading...">
2584+
<Foo suspend={false} />
2585+
</Suspense>
2586+
);
2587+
}
2588+
2589+
const thenable = {then() {}};
2590+
2591+
let foo;
2592+
class Foo extends React.Component {
2593+
state = {suspend: false};
2594+
render() {
2595+
foo = this;
2596+
2597+
if (this.state.suspend) {
2598+
Scheduler.unstable_yieldValue('Suspend!');
2599+
throw thenable;
2600+
}
2601+
2602+
return <Text text="Foo" />;
2603+
}
2604+
}
2605+
2606+
const root = ReactNoop.createRoot();
2607+
await ReactNoop.act(async () => {
2608+
root.render(<App />);
2609+
});
2610+
2611+
expect(Scheduler).toHaveYielded(['Foo']);
2612+
2613+
await ReactNoop.act(async () => {
2614+
foo.setState({suspend: true});
2615+
2616+
// In the regression that this covers, we would neglect to reset the
2617+
// current debug phase after suspending (in the catch block), so React
2618+
// thinks we're still inside the render phase.
2619+
expect(Scheduler).toFlushAndYieldThrough(['Suspend!']);
2620+
2621+
// Then when this setState happens, React would incorrectly fire a warning
2622+
// about updates that happen the render phase (only fired by classes).
2623+
foo.setState({suspend: false});
2624+
});
2625+
2626+
expect(root).toMatchRenderedOutput(<span prop="Foo" />);
2627+
});
25792628
});

0 commit comments

Comments
 (0)