diff --git a/packages/react-reconciler/src/ReactFiberThrow.js b/packages/react-reconciler/src/ReactFiberThrow.js
index 0216363631..d04bff34fa 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.js
@@ -12,7 +12,10 @@ import type {Lane, Lanes} from './ReactFiberLane';
import type {CapturedValue} from './ReactCapturedValue';
import type {Update} from './ReactFiberClassUpdateQueue';
import type {Wakeable} from 'shared/ReactTypes';
-import type {OffscreenQueue} from './ReactFiberOffscreenComponent';
+import type {
+ OffscreenQueue,
+ OffscreenState,
+} from './ReactFiberOffscreenComponent';
import type {RetryQueue} from './ReactFiberSuspenseComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -676,6 +679,21 @@ function throwException(
return false;
}
break;
+ case OffscreenComponent: {
+ const offscreenState: OffscreenState | null =
+ (workInProgress.memoizedState: any);
+ if (offscreenState !== null) {
+ // An error was thrown inside a hidden Offscreen boundary. This should
+ // not be allowed to escape into the visible part of the UI. Mark the
+ // boundary with ShouldCapture to abort the ongoing prerendering
+ // attempt. This is the same flag would be set if something were to
+ // suspend. It will be cleared the next time the boundary
+ // is attempted.
+ workInProgress.flags |= ShouldCapture;
+ return false;
+ }
+ break;
+ }
default:
break;
}
diff --git a/packages/react-reconciler/src/__tests__/ActivityErrorHandling-test.js b/packages/react-reconciler/src/__tests__/ActivityErrorHandling-test.js
new file mode 100644
index 0000000000..ac6d1f9b88
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ActivityErrorHandling-test.js
@@ -0,0 +1,99 @@
+let React;
+let ReactNoop;
+let Scheduler;
+let act;
+let Activity;
+let useState;
+let assertLog;
+
+describe('Activity error handling', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ React = require('react');
+ ReactNoop = require('react-noop-renderer');
+ Scheduler = require('scheduler');
+ act = require('internal-test-utils').act;
+ Activity = React.Activity;
+ useState = React.useState;
+
+ const InternalTestUtils = require('internal-test-utils');
+ assertLog = InternalTestUtils.assertLog;
+ });
+
+ function Text({text}) {
+ Scheduler.log(text);
+ return text;
+ }
+
+ // @gate enableActivity
+ it(
+ 'errors inside a hidden Activity do not escape in the visible part ' +
+ 'of the UI',
+ async () => {
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ static getDerivedStateFromError(error) {
+ return {error};
+ }
+ render() {
+ if (this.state.error) {
+ return (
+