@samitouri / QOS-React / commits / 3c69a18814

Recover from errors with a boundary in completion phase (#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

Dan Abramov committed Nov 6, 2018 at 23:38 UTC 3c69a1881421d46d738e2782fe3ec0ae9b5cb39c
2 files changed +39 -4
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+15
@@ -2149,4 +2149,19 @@ describe('ReactErrorBoundaries', () => {
2149 expect(componentDidCatchError).toBe(thrownError);
2150 expect(getDerivedStateFromErrorError).toBe(thrownError);
2151 });
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 + });
2167 });
packages/react-reconciler/src/ReactFiberScheduler.js
+24 -4
@@ -275,11 +275,13 @@ let interruptedBy: Fiber | null = null;
275
276 let stashedWorkInProgressProperties;
277 let replayUnitOfWork;
278 +let mayReplayFailedUnitOfWork;
279 let isReplayingFailedUnitOfWork;
280 let originalReplayError;
281 let rethrowOriginalError;
282 if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
283 stashedWorkInProgressProperties = null;
284 + mayReplayFailedUnitOfWork = true;
285 isReplayingFailedUnitOfWork = false;
286 originalReplayError = null;
287 replayUnitOfWork = (
@@ -952,18 +954,22 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
954 const siblingFiber = workInProgress.sibling;
955
956 if ((workInProgress.effectTag & Incomplete) === NoEffect) {
957 + if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
958 + // Don't replay if it fails during completion phase.
959 + mayReplayFailedUnitOfWork = false;
960 + }
961 // This fiber completed.
962 + // Remember we're completing this unit so we can find a boundary if it fails.
963 + nextUnitOfWork = workInProgress;
964 if (enableProfilerTimer) {
965 if (workInProgress.mode & ProfileMode) {
966 startProfilerTimer(workInProgress);
967 }
960 -
968 nextUnitOfWork = completeWork(
969 current,
970 workInProgress,
971 nextRenderExpirationTime,
972 );
966 -
973 if (workInProgress.mode & ProfileMode) {
974 // Update render duration assuming we didn't error.
975 stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
@@ -975,6 +981,10 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
981 nextRenderExpirationTime,
982 );
983 }
984 + if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
985 + // We're out of completion phase so replaying is fine now.
986 + mayReplayFailedUnitOfWork = true;
987 + }
988 stopWorkTimer(workInProgress);
989 resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
990 if (__DEV__) {
@@ -1292,6 +1302,14 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
1302 resetContextDependences();
1303 resetHooks();
1304
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 +
1313 if (nextUnitOfWork === null) {
1314 // This is a fatal error.
1315 didFatal = true;
@@ -1303,9 +1321,11 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
1321 (resetCurrentlyProcessingQueue: any)();
1322 }
1323
1306 - const failedUnitOfWork: Fiber = nextUnitOfWork;
1324 if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1308 - replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
1325 + if (mayReplay) {
1326 + const failedUnitOfWork: Fiber = nextUnitOfWork;
1327 + replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
1328 + }
1329 }
1330
1331 // TODO: we already know this isn't true in some cases.