@samitouri / QOS-React / commits / 892c68605c

[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: