diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index 96a88cf31e73..9a01ae110a64 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -1,5 +1,5 @@ import type { ReportDialogOptions, Scope } from '@sentry/browser'; -import { captureException, showReportDialog, withScope } from '@sentry/browser'; +import { captureException, getCurrentHub, showReportDialog, withScope } from '@sentry/browser'; import { isError, logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; @@ -94,9 +94,26 @@ function setCause(error: Error & { cause?: Error }, cause: Error): void { class ErrorBoundary extends React.Component { public state: ErrorBoundaryState = INITIAL_STATE; + private readonly _openFallbackReportDialog: boolean = true; + + private _lastEventId?: string; + + public constructor(props: ErrorBoundaryProps) { + super(props); + + const client = getCurrentHub().getClient(); + if (client && client.on && props.showDialog) { + this._openFallbackReportDialog = false; + client.on('afterSendEvent', event => { + if (!event.type && event.event_id === this._lastEventId) { + showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId }); + } + }); + } + } + public componentDidCatch(error: Error & { cause?: Error }, { componentStack }: React.ErrorInfo): void { const { beforeCapture, onError, showDialog, dialogOptions } = this.props; - withScope(scope => { // If on React version >= 17, create stack trace from componentStack param and links // to to the original error using `error.cause` otherwise relies on error param for stacktrace. @@ -123,7 +140,10 @@ class ErrorBoundary extends React.Component { @@ -82,6 +83,7 @@ describe('ErrorBoundary', () => { afterEach(() => { mockCaptureException.mockClear(); mockShowReportDialog.mockClear(); + mockClientOn.mockClear(); }); it('renders null if not given a valid `fallback` prop', () => { @@ -405,7 +407,34 @@ describe('ErrorBoundary', () => { expect(mockCaptureException).toHaveBeenCalledTimes(1); }); - it('shows a Sentry Report Dialog with correct options', () => { + it('shows a Sentry Report Dialog with correct options if client does not have hooks', () => { + expect(getCurrentHub().getClient()).toBeUndefined(); + + const options = { title: 'custom title' }; + render( + You have hit an error

} showDialog dialogOptions={options}> +

children

+
, + ); + + expect(mockShowReportDialog).toHaveBeenCalledTimes(0); + + const btn = screen.getByTestId('errorBtn'); + fireEvent.click(btn); + + expect(mockShowReportDialog).toHaveBeenCalledTimes(1); + expect(mockShowReportDialog).toHaveBeenCalledWith({ ...options, eventId: EVENT_ID }); + }); + + it('shows a Sentry Report Dialog with correct options if client has hooks', () => { + let callback: any; + const hub = getCurrentHub(); + // @ts-ignore mock client + hub.bindClient({ + on: (name: string, cb: any) => { + callback = cb; + }, + }); const options = { title: 'custom title' }; render( You have hit an error

} showDialog dialogOptions={options}> @@ -418,8 +447,13 @@ describe('ErrorBoundary', () => { const btn = screen.getByTestId('errorBtn'); fireEvent.click(btn); + // Simulate hook being fired + callback({ event_id: EVENT_ID }); + expect(mockShowReportDialog).toHaveBeenCalledTimes(1); expect(mockShowReportDialog).toHaveBeenCalledWith({ ...options, eventId: EVENT_ID }); + + hub.bindClient(undefined); }); it('resets to initial state when reset', async () => {