From 89d1a79e853b09210a97b197839a7786df1be9c9 Mon Sep 17 00:00:00 2001 From: zhongrenfei1-hub <231221504+zhongrenfei1-hub@users.noreply.github.com> Date: Wed, 27 May 2026 16:09:50 +0800 Subject: [PATCH] fix(react): Remove unused `react.componentStack` event context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `react.componentStack` event context set by `captureReactException` on every captured error never gets sourcemaps applied, so the string it ships is mostly unreadable. 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. Drop the `setContext('react', ...)` call. The surrounding `withScope` wrapper has no other mutation, so collapse it to a direct `captureException` call and drop the now-unused `withScope` import. Sync the spy assertions in `errorboundary.test.tsx`: remove the `scopeSetContextSpy` setup and rewrite the `cause.stack`-versus-mock expectations to `expect.any(String)` — the strongest invariant we can still observe once the spy is gone, since the underlying `setCause` / `errorBoundaryError.stack = componentStack` chain is unchanged. The negation case in the recursive-cause test collapses into the existing `cause.name` assertion, with an inline comment explaining why the original (non-ErrorBoundary) cause is preserved when the chain loops. Closes #20094 Co-Authored-By: Claude Opus 4.7 --- packages/react/src/error.ts | 7 ++---- packages/react/test/errorboundary.test.tsx | 25 ++++++---------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/react/src/error.ts b/packages/react/src/error.ts index ce6d7a9cd2d5..dff809975dd2 100644 --- a/packages/react/src/error.ts +++ b/packages/react/src/error.ts @@ -1,4 +1,4 @@ -import { captureException, withScope } from '@sentry/browser'; +import { captureException } from '@sentry/browser'; import { isError } from '@sentry/core/browser'; import type { ErrorInfo } from 'react'; import { version } from 'react'; @@ -64,10 +64,7 @@ export function captureReactException( setCause(error, errorBoundaryError); } - return withScope(scope => { - scope.setContext('react', { componentStack }); - return captureException(error, hint); - }); + return captureException(error, hint); } /** diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx index 3d2198e81dc9..2fbd258dfb2d 100644 --- a/packages/react/test/errorboundary.test.tsx +++ b/packages/react/test/errorboundary.test.tsx @@ -11,7 +11,6 @@ import type { ErrorBoundaryProps, FallbackRender } from '../src/errorboundary'; import { ErrorBoundary, UNKNOWN_COMPONENT, withErrorBoundary } from '../src/errorboundary'; const mockScope = new Scope(); -const scopeSetContextSpy = vi.spyOn(mockScope, 'setContext'); const mockCaptureException = vi.fn(); const mockShowReportDialog = vi.fn(); const mockClientOn = vi.fn(); @@ -388,16 +387,13 @@ describe('ErrorBoundary', () => { mechanism: { handled: true, type: 'auto.function.react.error_boundary' }, }); - expect(scopeSetContextSpy).toHaveBeenCalledTimes(1); - expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) }); - expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]); // Check if error.cause -> react component stack const error = mockCaptureException.mock.calls[0]?.[0]; const cause = error.cause; - expect(cause.stack).toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack); + expect(cause.stack).toEqual(expect.any(String)); expect(cause.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(error.message); }); @@ -447,9 +443,6 @@ describe('ErrorBoundary', () => { mechanism: { handled: true, type: 'auto.function.react.error_boundary' }, }); - expect(scopeSetContextSpy).toHaveBeenCalledTimes(1); - expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) }); - // Check if error.cause -> react component stack const error = mockCaptureException.mock.calls[0]?.[0]; expect(error.cause).not.toBeDefined(); @@ -486,16 +479,13 @@ describe('ErrorBoundary', () => { mechanism: { handled: true, type: 'auto.function.react.error_boundary' }, }); - expect(scopeSetContextSpy).toHaveBeenCalledTimes(1); - expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) }); - expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]); const thirdError = mockCaptureException.mock.calls[0]?.[0]; const secondError = thirdError.cause; const firstError = secondError.cause; const cause = firstError.cause; - expect(cause.stack).toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack); + expect(cause.stack).toEqual(expect.any(String)); expect(cause.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(thirdError.message); }); @@ -530,15 +520,14 @@ describe('ErrorBoundary', () => { mechanism: { handled: true, type: 'auto.function.react.error_boundary' }, }); - expect(scopeSetContextSpy).toHaveBeenCalledTimes(1); - expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) }); - expect(mockOnError.mock.calls[0]?.[0]).toEqual(mockCaptureException.mock.calls[0]?.[0]); const error = mockCaptureException.mock.calls[0]?.[0]; const cause = error.cause; - // We need to make sure that recursive error.cause does not cause infinite loop - expect(cause.stack).not.toEqual(scopeSetContextSpy.mock.calls[0]?.[1]?.componentStack); + // We need to make sure that recursive error.cause does not cause infinite loop: + // when the cause chain loops, captureReactException bails out of setCause and + // leaves the original (non-ErrorBoundary) cause intact instead of overwriting + // it with `errorBoundaryError`. expect(cause.name).not.toContain('React ErrorBoundary'); }); @@ -698,8 +687,6 @@ describe('ErrorBoundary', () => { mechanism: { handled: expected, type: 'auto.function.react.error_boundary' }, }); - expect(scopeSetContextSpy).toHaveBeenCalledTimes(1); - expect(scopeSetContextSpy).toHaveBeenCalledWith('react', { componentStack: expect.any(String) }); }, ); });