Skip to content

Commit 367f779

Browse files
authored
fix(react): Only show report dialog if event was sent to Sentry (#7754)
1 parent 2531853 commit 367f779

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

packages/react/src/errorboundary.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ReportDialogOptions, Scope } from '@sentry/browser';
2-
import { captureException, showReportDialog, withScope } from '@sentry/browser';
2+
import { captureException, getCurrentHub, showReportDialog, withScope } from '@sentry/browser';
33
import { isError, logger } from '@sentry/utils';
44
import hoistNonReactStatics from 'hoist-non-react-statics';
55
import * as React from 'react';
@@ -94,9 +94,26 @@ function setCause(error: Error & { cause?: Error }, cause: Error): void {
9494
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
9595
public state: ErrorBoundaryState = INITIAL_STATE;
9696

97+
private readonly _openFallbackReportDialog: boolean = true;
98+
99+
private _lastEventId?: string;
100+
101+
public constructor(props: ErrorBoundaryProps) {
102+
super(props);
103+
104+
const client = getCurrentHub().getClient();
105+
if (client && client.on && props.showDialog) {
106+
this._openFallbackReportDialog = false;
107+
client.on('afterSendEvent', event => {
108+
if (!event.type && event.event_id === this._lastEventId) {
109+
showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId });
110+
}
111+
});
112+
}
113+
}
114+
97115
public componentDidCatch(error: Error & { cause?: Error }, { componentStack }: React.ErrorInfo): void {
98116
const { beforeCapture, onError, showDialog, dialogOptions } = this.props;
99-
100117
withScope(scope => {
101118
// If on React version >= 17, create stack trace from componentStack param and links
102119
// to to the original error using `error.cause` otherwise relies on error param for stacktrace.
@@ -123,7 +140,10 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
123140
onError(error, componentStack, eventId);
124141
}
125142
if (showDialog) {
126-
showReportDialog({ ...dialogOptions, eventId });
143+
this._lastEventId = eventId;
144+
if (this._openFallbackReportDialog) {
145+
showReportDialog({ ...dialogOptions, eventId });
146+
}
127147
}
128148

129149
// componentDidCatch is used over getDerivedStateFromError

packages/react/test/errorboundary.test.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Scope } from '@sentry/browser';
1+
import { getCurrentHub, Scope } from '@sentry/browser';
22
import { fireEvent, render, screen } from '@testing-library/react';
33
import * as React from 'react';
44
import { useState } from 'react';
@@ -8,6 +8,7 @@ import { ErrorBoundary, isAtLeastReact17, UNKNOWN_COMPONENT, withErrorBoundary }
88

99
const mockCaptureException = jest.fn();
1010
const mockShowReportDialog = jest.fn();
11+
const mockClientOn = jest.fn();
1112
const EVENT_ID = 'test-id-123';
1213

1314
jest.mock('@sentry/browser', () => {
@@ -82,6 +83,7 @@ describe('ErrorBoundary', () => {
8283
afterEach(() => {
8384
mockCaptureException.mockClear();
8485
mockShowReportDialog.mockClear();
86+
mockClientOn.mockClear();
8587
});
8688

8789
it('renders null if not given a valid `fallback` prop', () => {
@@ -405,7 +407,34 @@ describe('ErrorBoundary', () => {
405407
expect(mockCaptureException).toHaveBeenCalledTimes(1);
406408
});
407409

408-
it('shows a Sentry Report Dialog with correct options', () => {
410+
it('shows a Sentry Report Dialog with correct options if client does not have hooks', () => {
411+
expect(getCurrentHub().getClient()).toBeUndefined();
412+
413+
const options = { title: 'custom title' };
414+
render(
415+
<TestApp fallback={<p>You have hit an error</p>} showDialog dialogOptions={options}>
416+
<h1>children</h1>
417+
</TestApp>,
418+
);
419+
420+
expect(mockShowReportDialog).toHaveBeenCalledTimes(0);
421+
422+
const btn = screen.getByTestId('errorBtn');
423+
fireEvent.click(btn);
424+
425+
expect(mockShowReportDialog).toHaveBeenCalledTimes(1);
426+
expect(mockShowReportDialog).toHaveBeenCalledWith({ ...options, eventId: EVENT_ID });
427+
});
428+
429+
it('shows a Sentry Report Dialog with correct options if client has hooks', () => {
430+
let callback: any;
431+
const hub = getCurrentHub();
432+
// @ts-ignore mock client
433+
hub.bindClient({
434+
on: (name: string, cb: any) => {
435+
callback = cb;
436+
},
437+
});
409438
const options = { title: 'custom title' };
410439
render(
411440
<TestApp fallback={<p>You have hit an error</p>} showDialog dialogOptions={options}>
@@ -418,8 +447,13 @@ describe('ErrorBoundary', () => {
418447
const btn = screen.getByTestId('errorBtn');
419448
fireEvent.click(btn);
420449

450+
// Simulate hook being fired
451+
callback({ event_id: EVENT_ID });
452+
421453
expect(mockShowReportDialog).toHaveBeenCalledTimes(1);
422454
expect(mockShowReportDialog).toHaveBeenCalledWith({ ...options, eventId: EVENT_ID });
455+
456+
hub.bindClient(undefined);
423457
});
424458

425459
it('resets to initial state when reset', async () => {

0 commit comments

Comments
 (0)