Skip to content

Commit 72bf32d

Browse files
Renfeizhongrenfei1-hubclaudenicohrubec
authored
fix(react): Remove unused react.componentStack event context (#21183)
## Summary Removes the `react.componentStack` event context set by `captureReactException`. The shipped string never has sourcemaps applied, so it's mostly unreadable in the Sentry UI. For React >= 17 the same component stack is already attached via `error.cause` (which *does* get sourcemaps); for React < 17 it offers little value either. Maintainer-confirmed in #20094. ## Root cause `captureReactException` wrapped the capture in `withScope` solely to attach the `react.componentStack` context. With that context gone the `withScope` wrapper has no other mutation, so it collapses to a direct `captureException` call (and `withScope` drops out of the imports). Test sync: `scopeSetContextSpy` in `errorboundary.test.tsx` is removed. The two `expect(cause.stack).toEqual(scopeSetContextSpy.mock.calls[...])` assertions become `expect.any(String)` — the strongest invariant left now that the spy is gone, since the underlying `setCause` / `errorBoundaryError.stack = componentStack` chain is unchanged. The recursive-cause negation collapses into the existing `cause.name` assertion, with an inline comment explaining why the original cause is preserved when the chain loops. Fixes #20094 --------- Co-authored-by: zhongrenfei1-hub <231221504+zhongrenfei1-hub@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
1 parent 6480127 commit 72bf32d

2 files changed

Lines changed: 8 additions & 26 deletions

File tree

packages/react/src/error.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, withScope } from '@sentry/browser';
1+
import { captureException } from '@sentry/browser';
22
import { isError } from '@sentry/core/browser';
33
import type { ErrorInfo } from 'react';
44
import { version } from 'react';
@@ -64,10 +64,7 @@ export function captureReactException(
6464
setCause(error, errorBoundaryError);
6565
}
6666

67-
return withScope(scope => {
68-
scope.setContext('react', { componentStack });
69-
return captureException(error, hint);
70-
});
67+
return captureException(error, hint);
7168
}
7269

7370
/**

packages/react/test/errorboundary.test.tsx

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type { ErrorBoundaryProps, FallbackRender } from '../src/errorboundary';
1111
import { ErrorBoundary, UNKNOWN_COMPONENT, withErrorBoundary } from '../src/errorboundary';
1212

1313
const mockScope = new Scope();
14-
const scopeSetContextSpy = vi.spyOn(mockScope, 'setContext');
1514
const mockCaptureException = vi.fn();
1615
const mockShowReportDialog = vi.fn();
1716
const mockClientOn = vi.fn();
@@ -221,7 +220,6 @@ describe('ErrorBoundary', () => {
221220
mockCaptureException.mockClear();
222221
mockShowReportDialog.mockClear();
223222
mockClientOn.mockClear();
224-
(mockScope.setContext as any).mockClear();
225223
});
226224

227225
it('renders null if not given a valid `fallback` prop', () => {
@@ -388,16 +386,13 @@ describe('ErrorBoundary', () => {
388386
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
389387
});
390388

391-
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
392-
expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) });
393-
394389
expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]);
395390

396391
// Check if error.cause -> react component stack
397392
const error = mockCaptureException.mock.calls[0]?.[0];
398393
const cause = error.cause;
399394

400-
expect(cause.stack).toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack);
395+
expect(cause.stack).toEqual(expect.any(String));
401396
expect(cause.name).toContain('React ErrorBoundary');
402397
expect(cause.message).toEqual(error.message);
403398
});
@@ -447,9 +442,6 @@ describe('ErrorBoundary', () => {
447442
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
448443
});
449444

450-
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
451-
expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) });
452-
453445
// Check if error.cause -> react component stack
454446
const error = mockCaptureException.mock.calls[0]?.[0];
455447
expect(error.cause).not.toBeDefined();
@@ -486,16 +478,13 @@ describe('ErrorBoundary', () => {
486478
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
487479
});
488480

489-
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
490-
expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) });
491-
492481
expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]);
493482

494483
const thirdError = mockCaptureException.mock.calls[0]?.[0];
495484
const secondError = thirdError.cause;
496485
const firstError = secondError.cause;
497486
const cause = firstError.cause;
498-
expect(cause.stack).toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack);
487+
expect(cause.stack).toEqual(expect.any(String));
499488
expect(cause.name).toContain('React ErrorBoundary');
500489
expect(cause.message).toEqual(thirdError.message);
501490
});
@@ -530,15 +519,14 @@ describe('ErrorBoundary', () => {
530519
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
531520
});
532521

533-
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
534-
expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) });
535-
536522
expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]);
537523

538524
const error = mockCaptureException.mock.calls[0]?.[0];
539525
const cause = error.cause;
540-
// We need to make sure that recursive error.cause does not cause infinite loop
541-
expect(cause.stack).not.toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack);
526+
// We need to make sure that recursive error.cause does not cause infinite loop:
527+
// when the cause chain loops, captureReactException bails out of setCause and
528+
// leaves the original (non-ErrorBoundary) cause intact instead of overwriting
529+
// it with `errorBoundaryError`.
542530
expect(cause.name).not.toContain('React ErrorBoundary');
543531
});
544532

@@ -697,9 +685,6 @@ describe('ErrorBoundary', () => {
697685
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
698686
mechanism: { handled: expected, type: 'auto.function.react.error_boundary' },
699687
});
700-
701-
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
702-
expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) });
703688
},
704689
);
705690
});

0 commit comments

Comments
 (0)