Skip to content

Commit 1ecb349

Browse files
committed
ref(react): Don't throw error when no fallback given
1 parent 2f638a4 commit 1ecb349

File tree

2 files changed

+45
-47
lines changed

2 files changed

+45
-47
lines changed

packages/react/src/errorboundary.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as Sentry from '@sentry/browser';
22
import * as hoistNonReactStatic from 'hoist-non-react-statics';
33
import * as React from 'react';
44

5-
export const FALLBACK_ERR_MESSAGE = 'No fallback component has been set';
65
export const UNKNOWN_COMPONENT = 'unknown';
76

87
export type ErrorBoundaryProps = {
@@ -94,7 +93,8 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
9493
return fallback;
9594
}
9695

97-
throw new Error(FALLBACK_ERR_MESSAGE);
96+
// Fail gracefully if no fallback provided
97+
return null;
9898
}
9999

100100
return this.props.children;
@@ -113,7 +113,7 @@ function withErrorBoundary<P extends object>(
113113
</ErrorBoundary>
114114
);
115115

116-
Wrapped.displayName = `boundary(${componentDisplayName})`;
116+
Wrapped.displayName = `errorBoundary(${componentDisplayName})`;
117117

118118
// Copy over static methods from Wrapped component to Profiler HOC
119119
// See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over

packages/react/test/errorboundary.test.tsx

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { fireEvent, render, screen } from '@testing-library/react';
22
import * as React from 'react';
33

4-
import { ErrorBoundary, ErrorBoundaryProps, FALLBACK_ERR_MESSAGE } from '../src/errorboundary';
4+
import { ErrorBoundary, ErrorBoundaryProps } from '../src/errorboundary';
55

66
const mockCaptureException = jest.fn();
77
const mockShowReportDialog = jest.fn();
@@ -35,45 +35,43 @@ function Bam(): JSX.Element {
3535
}
3636

3737
describe('ErrorBoundary', () => {
38-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
38+
jest.spyOn(console, 'error').mockImplementation();
3939

4040
afterEach(() => {
41-
consoleErrorSpy.mockClear();
4241
mockCaptureException.mockClear();
4342
mockShowReportDialog.mockClear();
4443
});
4544

46-
it('throws an error if not given a valid `fallbackRender` prop', () => {
47-
expect(() => {
48-
render(
49-
// @ts-ignore
50-
<ErrorBoundary fallbackRender={'ok'}>
51-
<Bam />
52-
</ErrorBoundary>,
53-
);
54-
}).toThrowError(FALLBACK_ERR_MESSAGE);
55-
expect(consoleErrorSpy).toHaveBeenCalled();
45+
it('renders null if not given a valid `fallbackRender` prop', () => {
46+
const { container } = render(
47+
// @ts-ignore
48+
<ErrorBoundary fallbackRender={'ok'}>
49+
<Bam />
50+
</ErrorBoundary>,
51+
);
52+
53+
expect(container.innerHTML).toBe('');
5654
});
5755

58-
it('throws an error if not given a valid `fallback` prop', () => {
59-
expect(() => {
60-
render(
61-
<ErrorBoundary fallback={new Error('true')}>
62-
<Bam />
63-
</ErrorBoundary>,
64-
);
65-
}).toThrowError(FALLBACK_ERR_MESSAGE);
66-
expect(consoleErrorSpy).toHaveBeenCalled();
56+
it('renders null if not given a valid `fallback` prop', () => {
57+
const { container } = render(
58+
// @ts-ignore
59+
<ErrorBoundary fallback={new Error('true')}>
60+
<Bam />
61+
</ErrorBoundary>,
62+
);
63+
64+
expect(container.innerHTML).toBe('');
6765
});
6866

69-
it('does not throw an error if a fallback is given', () => {
70-
expect(() => {
71-
render(
72-
<ErrorBoundary fallback={<h1>Error Component</h1>}>
73-
<h1>children</h1>
74-
</ErrorBoundary>,
75-
);
76-
}).not.toThrowError();
67+
it('renders a fallback on error', () => {
68+
const { container } = render(
69+
// @ts-ignore
70+
<ErrorBoundary fallback={<h1>Error Component</h1>}>
71+
<Bam />
72+
</ErrorBoundary>,
73+
);
74+
expect(container.innerHTML).toBe('<h1>Error Component</h1>');
7775
});
7876

7977
it('calls `onMount` when mounted', () => {
@@ -102,36 +100,36 @@ describe('ErrorBoundary', () => {
102100
});
103101

104102
it('renders children correctly when there is no error', () => {
105-
const { baseElement } = render(
103+
const { container } = render(
106104
<ErrorBoundary fallback={<h1>Error Component</h1>}>
107105
<h1>children</h1>
108106
</ErrorBoundary>,
109107
);
110108

111-
expect(baseElement.outerHTML).toContain('<h1>children</h1>');
109+
expect(container.innerHTML).toBe('<h1>children</h1>');
112110
});
113111

114112
describe('fallback', () => {
115113
it('renders a fallback component', async () => {
116-
const { baseElement } = render(
114+
const { container } = render(
117115
<TestApp fallback={<p>You have hit an error</p>}>
118116
<h1>children</h1>
119117
</TestApp>,
120118
);
121119

122-
expect(baseElement.outerHTML).toContain('<h1>children</h1>');
120+
expect(container.innerHTML).toContain('<h1>children</h1>');
123121

124122
const btn = screen.getByTestId('errorBtn');
125123
fireEvent.click(btn);
126124

127-
expect(baseElement.outerHTML).not.toContain('<h1>children</h1>');
128-
expect(baseElement.outerHTML).toContain('<p>You have hit an error</p>');
125+
expect(container.innerHTML).not.toContain('<h1>children</h1>');
126+
expect(container.innerHTML).toBe('<p>You have hit an error</p>');
129127
});
130128

131129
it('renders a fallbackRender component', async () => {
132130
let errorString = '';
133131
let compStack = '';
134-
const { baseElement } = render(
132+
const { container } = render(
135133
<TestApp
136134
fallbackRender={({ error, componentStack }) => {
137135
if (error && componentStack) {
@@ -145,13 +143,13 @@ describe('ErrorBoundary', () => {
145143
</TestApp>,
146144
);
147145

148-
expect(baseElement.outerHTML).toContain('<h1>children</h1>');
146+
expect(container.innerHTML).toContain('<h1>children</h1>');
149147

150148
const btn = screen.getByTestId('errorBtn');
151149
fireEvent.click(btn);
152150

153-
expect(baseElement.outerHTML).not.toContain('<h1>children</h1');
154-
expect(baseElement.outerHTML).toContain('<div>Fallback here</div>');
151+
expect(container.innerHTML).not.toContain('<h1>children</h1');
152+
expect(container.innerHTML).toBe('<div>Fallback here</div>');
155153

156154
expect(errorString).toBe('Error: boom');
157155
expect(compStack).toBe(`
@@ -181,7 +179,7 @@ describe('ErrorBoundary', () => {
181179

182180
expect(mockCaptureException).toHaveBeenCalledTimes(1);
183181
expect(mockCaptureException).toHaveBeenCalledWith(expect.any(Error), {
184-
contexts: { componentStack: expect.any(String) },
182+
contexts: { react: { componentStack: expect.any(String) } },
185183
});
186184
});
187185

@@ -204,7 +202,7 @@ describe('ErrorBoundary', () => {
204202

205203
it('resets to initial state when reset', () => {
206204
const mockOnReset = jest.fn();
207-
const { baseElement } = render(
205+
const { container } = render(
208206
<TestApp
209207
onReset={mockOnReset}
210208
fallbackRender={({ resetError }) => <button data-testid="reset" onClick={resetError} />}
@@ -213,13 +211,13 @@ describe('ErrorBoundary', () => {
213211
</TestApp>,
214212
);
215213

216-
expect(baseElement.outerHTML).toContain('<h1>children</h1>');
214+
expect(container.innerHTML).toContain('<h1>children</h1>');
217215
expect(mockOnReset).toHaveBeenCalledTimes(0);
218216

219217
const btn = screen.getByTestId('errorBtn');
220218
fireEvent.click(btn);
221219

222-
expect(baseElement.outerHTML).toContain('<button data-testid="reset">');
220+
expect(container.innerHTML).toContain('<button data-testid="reset">');
223221
expect(mockOnReset).toHaveBeenCalledTimes(0);
224222

225223
const reset = screen.getByTestId('reset');

0 commit comments

Comments
 (0)