Skip to content

Commit 5b9991d

Browse files
committed
[suspense][error handling] Add failing unit test
Covers an edge case where an error is thrown inside the complete phase of a component that is in the return path of a component that suspends. The second error should also be handled (i.e. able to be captured by an error boundary. The test is currently failing because there's a call to `completeUnitOfWork` inside the main render phase `catch` block. That call is not itself wrapped in try-catch, so anything that throws is treated as a fatal/unhandled error. I believe this bug is only observable if something in the host config throws; and, only in legacy mode, because in concurrent/batched mode, `completeUnitOfWork` on fiber that throws follows the "unwind" path only, not the "complete" path, and the "unwind" path does not call any host config methods.
1 parent 494300b commit 5b9991d

File tree

1 file changed

+33
-0
lines changed

1 file changed

+33
-0
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,39 @@ describe('ReactSuspenseWithNoopRenderer', () => {
13921392
expect(Scheduler).toFlushExpired(['Hi']);
13931393
});
13941394
}
1395+
1396+
it('handles errors in the return path of a component that suspends', async () => {
1397+
// Covers an edge case where an error is thrown inside the complete phase
1398+
// of a component that is in the return path of a component that suspends.
1399+
// The second error should also be handled (i.e. able to be captured by
1400+
// an error boundary.
1401+
class ErrorBoundary extends React.Component {
1402+
state = {error: null};
1403+
static getDerivedStateFromError(error, errorInfo) {
1404+
return {error};
1405+
}
1406+
render() {
1407+
if (this.state.error) {
1408+
return `Caught an error: ${this.state.error.message}`;
1409+
}
1410+
return this.props.children;
1411+
}
1412+
}
1413+
1414+
ReactNoop.renderLegacySyncRoot(
1415+
<Suspense fallback="Loading...">
1416+
<ErrorBoundary>
1417+
<errorInCompletePhase>
1418+
<AsyncText ms={1000} text="Async" />
1419+
</errorInCompletePhase>
1420+
</ErrorBoundary>
1421+
</Suspense>,
1422+
);
1423+
1424+
expect(ReactNoop).toMatchRenderedOutput(
1425+
'Caught an error: Error in host config.',
1426+
);
1427+
});
13951428
});
13961429

13971430
it('does not call lifecycles of a suspended component', async () => {

0 commit comments

Comments
 (0)