Skip to content

Commit 3c69a18

Browse files
authored
Recover from errors with a boundary in completion phase (facebook#14104)
* Recover from errors with a boundary in completion phase * Use a separate field for completing unit of work * Use a simpler fix with one boolean * Reoder conditions * Clarify which paths are DEV-only * Move duplicated line out * Make it clearer this code is DEV-only
1 parent b020fb1 commit 3c69a18

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js

+15
Original file line numberDiff line numberDiff line change
@@ -2149,4 +2149,19 @@ describe('ReactErrorBoundaries', () => {
21492149
expect(componentDidCatchError).toBe(thrownError);
21502150
expect(getDerivedStateFromErrorError).toBe(thrownError);
21512151
});
2152+
2153+
it('should catch errors from invariants in completion phase', () => {
2154+
const container = document.createElement('div');
2155+
ReactDOM.render(
2156+
<ErrorBoundary>
2157+
<input>
2158+
<div />
2159+
</input>
2160+
</ErrorBoundary>,
2161+
container,
2162+
);
2163+
expect(container.textContent).toContain(
2164+
'Caught an error: input is a void element tag',
2165+
);
2166+
});
21522167
});

packages/react-reconciler/src/ReactFiberScheduler.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,13 @@ let interruptedBy: Fiber | null = null;
275275

276276
let stashedWorkInProgressProperties;
277277
let replayUnitOfWork;
278+
let mayReplayFailedUnitOfWork;
278279
let isReplayingFailedUnitOfWork;
279280
let originalReplayError;
280281
let rethrowOriginalError;
281282
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
282283
stashedWorkInProgressProperties = null;
284+
mayReplayFailedUnitOfWork = true;
283285
isReplayingFailedUnitOfWork = false;
284286
originalReplayError = null;
285287
replayUnitOfWork = (
@@ -952,18 +954,22 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
952954
const siblingFiber = workInProgress.sibling;
953955

954956
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
957+
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
958+
// Don't replay if it fails during completion phase.
959+
mayReplayFailedUnitOfWork = false;
960+
}
955961
// This fiber completed.
962+
// Remember we're completing this unit so we can find a boundary if it fails.
963+
nextUnitOfWork = workInProgress;
956964
if (enableProfilerTimer) {
957965
if (workInProgress.mode & ProfileMode) {
958966
startProfilerTimer(workInProgress);
959967
}
960-
961968
nextUnitOfWork = completeWork(
962969
current,
963970
workInProgress,
964971
nextRenderExpirationTime,
965972
);
966-
967973
if (workInProgress.mode & ProfileMode) {
968974
// Update render duration assuming we didn't error.
969975
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
@@ -975,6 +981,10 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
975981
nextRenderExpirationTime,
976982
);
977983
}
984+
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
985+
// We're out of completion phase so replaying is fine now.
986+
mayReplayFailedUnitOfWork = true;
987+
}
978988
stopWorkTimer(workInProgress);
979989
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
980990
if (__DEV__) {
@@ -1292,6 +1302,14 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
12921302
resetContextDependences();
12931303
resetHooks();
12941304

1305+
// Reset in case completion throws.
1306+
// This is only used in DEV and when replaying is on.
1307+
let mayReplay;
1308+
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1309+
mayReplay = mayReplayFailedUnitOfWork;
1310+
mayReplayFailedUnitOfWork = true;
1311+
}
1312+
12951313
if (nextUnitOfWork === null) {
12961314
// This is a fatal error.
12971315
didFatal = true;
@@ -1303,9 +1321,11 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
13031321
(resetCurrentlyProcessingQueue: any)();
13041322
}
13051323

1306-
const failedUnitOfWork: Fiber = nextUnitOfWork;
13071324
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1308-
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
1325+
if (mayReplay) {
1326+
const failedUnitOfWork: Fiber = nextUnitOfWork;
1327+
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
1328+
}
13091329
}
13101330

13111331
// TODO: we already know this isn't true in some cases.

0 commit comments

Comments
 (0)