diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
index 3dde9c75bf03..f759fc60ef35 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
@@ -213,6 +213,67 @@ describe('ReactIncrementalErrorLogging', () => {
}).toThrow('logCapturedError error');
});
+ it('does not report internal Offscreen component for errors thrown during reconciliation inside Suspense', async () => {
+ // When a child of Suspense throws during reconciliation (not render),
+ // a Throw fiber is created whose .return is the internal Offscreen fiber.
+ // We should skip Offscreen since it's an internal
+ // implementation detail and walk up to Suspense instead.
+ const lazyChild = React.lazy(() => {
+ throw new Error('lazy init error');
+ });
+
+ await fakeAct(() => {
+ ReactNoop.render(
+ }>{lazyChild},
+ );
+ });
+ expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1);
+ expect(uncaughtExceptionMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ message: 'lazy init error',
+ }),
+ );
+ if (__DEV__) {
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn.mock.calls[0]).toEqual([
+ '%s\n\n%s\n',
+ 'An error occurred in the component.',
+ 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
+ 'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.',
+ ]);
+ }
+ });
+
+ it('does not report internal Offscreen component for errors thrown during reconciliation inside Activity', async () => {
+ // Same as the Suspense test above — Activity also wraps its children in
+ // an internal Offscreen fiber. The error message should show Activity,
+ // not Offscreen.
+ const lazyChild = React.lazy(() => {
+ throw new Error('lazy init error');
+ });
+
+ await fakeAct(() => {
+ ReactNoop.render(
+ {lazyChild},
+ );
+ });
+ expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1);
+ expect(uncaughtExceptionMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ message: 'lazy init error',
+ }),
+ );
+ if (__DEV__) {
+ expect(console.warn).toHaveBeenCalledTimes(1);
+ expect(console.warn.mock.calls[0]).toEqual([
+ '%s\n\n%s\n',
+ 'An error occurred in the component.',
+ 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
+ 'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.',
+ ]);
+ }
+ });
+
it('resets instance variables before unmounting failed node', async () => {
class ErrorBoundary extends React.Component {
state = {error: null};
diff --git a/packages/react-reconciler/src/getComponentNameFromFiber.js b/packages/react-reconciler/src/getComponentNameFromFiber.js
index 97124bbf5ba5..8719539cfc08 100644
--- a/packages/react-reconciler/src/getComponentNameFromFiber.js
+++ b/packages/react-reconciler/src/getComponentNameFromFiber.js
@@ -122,7 +122,10 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
}
return 'Mode';
case OffscreenComponent:
- return 'Offscreen';
+ if (fiber.return !== null) {
+ return getComponentNameFromFiber(fiber.return);
+ }
+ return null;
case Profiler:
return 'Profiler';
case ScopeComponent: