[fiber] bugfix - don't show <Offscreen> in error message. (#35763)
## Overview While building the RSC sandboxes I notice error messages like: > An error occurred in the `<Offscreen>` component This is an internal component so it should show either: > An error occurred in the `<Suspense>` component. > An error occurred in the `<Activity>` component. It should only happen when there's a lazy in the direct child position of a `<Suspense>` or `<Activity>` component.
Ricky committed
Feb 11, 2026 at 11:20 UTC
892c68605c46af0558cdd546782f23ad120ad0d4
2 files changed
+65
-1
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+61
@@ -213,6 +213,67 @@ describe('ReactIncrementalErrorLogging', () => {
213
}).toThrow('logCapturedError error');
214
});
215
216
+ it('does not report internal Offscreen component for errors thrown during reconciliation inside Suspense', async () => {
217
+ // When a child of Suspense throws during reconciliation (not render),
218
+ // a Throw fiber is created whose .return is the internal Offscreen fiber.
219
+ // We should skip Offscreen since it's an internal
220
+ // implementation detail and walk up to Suspense instead.
221
+ const lazyChild = React.lazy(() => {
222
+ throw new Error('lazy init error');
223
+ });
224
+
225
+ await fakeAct(() => {
226
+ ReactNoop.render(
227
+ <React.Suspense fallback={<div />}>{lazyChild}</React.Suspense>,
228
+ );
229
+ });
230
+ expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1);
231
+ expect(uncaughtExceptionMock).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ message: 'lazy init error',
234
+ }),
235
+ );
236
+ if (__DEV__) {
237
+ expect(console.warn).toHaveBeenCalledTimes(1);
238
+ expect(console.warn.mock.calls[0]).toEqual([
239
+ '%s\n\n%s\n',
240
+ 'An error occurred in the <Suspense> component.',
241
+ 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
242
+ 'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.',
243
+ ]);
244
+ }
245
+ });
246
+
247
+ it('does not report internal Offscreen component for errors thrown during reconciliation inside Activity', async () => {
248
+ // Same as the Suspense test above — Activity also wraps its children in
249
+ // an internal Offscreen fiber. The error message should show Activity,
250
+ // not Offscreen.
251
+ const lazyChild = React.lazy(() => {
252
+ throw new Error('lazy init error');
253
+ });
254
+
255
+ await fakeAct(() => {
256
+ ReactNoop.render(
257
+ <React.Activity mode="visible">{lazyChild}</React.Activity>,
258
+ );
259
+ });
260
+ expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1);
261
+ expect(uncaughtExceptionMock).toHaveBeenCalledWith(
262
+ expect.objectContaining({
263
+ message: 'lazy init error',
264
+ }),
265
+ );
266
+ if (__DEV__) {
267
+ expect(console.warn).toHaveBeenCalledTimes(1);
268
+ expect(console.warn.mock.calls[0]).toEqual([
269
+ '%s\n\n%s\n',
270
+ 'An error occurred in the <Activity> component.',
271
+ 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
272
+ 'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.',
273
+ ]);
274
+ }
275
+ });
276
+
277
it('resets instance variables before unmounting failed node', async () => {
278
class ErrorBoundary extends React.Component {
279
state = {error: null};
packages/react-reconciler/src/getComponentNameFromFiber.js
+4
-1
@@ -122,7 +122,10 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
122
}
123
return 'Mode';
124
case OffscreenComponent:
125
- return 'Offscreen';
125
+ if (fiber.return !== null) {
126
+ return getComponentNameFromFiber(fiber.return);
127
+ }
128
+ return null;
129
case Profiler:
130
return 'Profiler';
131
case ScopeComponent: