Skip to content

Commit e63e982

Browse files
authored
feat: drop Sentry (#7)
BREAKING CHANGE: sentry is no longer automatically set-up
1 parent d358bbb commit e63e982

File tree

9 files changed

+265
-506
lines changed

9 files changed

+265
-506
lines changed

package.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,10 @@
3737
"yalc.watch": "nodemon --watch 'src/**/*' --exec 'yarn yalc'"
3838
},
3939
"peerDependencies": {
40-
"react": ">=16.8",
41-
"react-dom": ">=16.8"
42-
},
43-
"dependencies": {
44-
"@sentry/react": "^6.13.2"
40+
"react": ">=16.8"
4541
},
42+
"dependencies": {},
4643
"devDependencies": {
47-
"@stoplight/reporter": "^1.7.6",
4844
"@stoplight/scripts": "^7.0.3",
4945
"@stoplight/storybook-config": "^2.0.4",
5046
"@types/enzyme": "^3.10.3",

src/ErrorBoundary.tsx

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,78 @@
1-
import { ErrorBoundary as SentryErrorBoundary } from '@sentry/react';
2-
import { FallbackRender } from '@sentry/react/dist/errorboundary';
31
import * as React from 'react';
2+
import { GenericReactErrorBoundaryError } from './error';
43
import { ErrorBoundaryContext } from './ErrorBoundaryProvider';
54
import { FallbackComponent } from './FallbackComponent';
6-
import { ErrorBoundaryProps, FallbackProps } from './types';
5+
import { ErrorBoundaryProps, ErrorBoundaryState } from './types';
76

8-
const wrapFallback = (Component: React.ElementType<FallbackProps>): FallbackRender => {
9-
return props => (
10-
<Component error={props.error} componentStack={props.componentStack} tryRecovering={props.resetError} />
11-
);
12-
};
7+
export class ErrorBoundary<P extends object = {}> extends React.PureComponent<
8+
P & ErrorBoundaryProps<P>,
9+
ErrorBoundaryState
10+
> {
11+
public static contextType = ErrorBoundaryContext;
12+
public context!: React.ContextType<typeof ErrorBoundaryContext>;
1313

14-
type GenericProps = Record<string, unknown>;
14+
public state = {
15+
error: null,
16+
componentStack: null,
17+
};
1518

16-
function usePrev(value: GenericProps) {
17-
const ref = React.useRef<GenericProps>();
18-
React.useEffect(() => {
19-
ref.current = value;
20-
}, [value]);
19+
public componentDidUpdate(prevProps: Readonly<P & ErrorBoundaryProps<P>>) {
20+
if (
21+
this.state.error !== null &&
22+
this.props.recoverableProps !== void 0 &&
23+
Array.isArray(this.props.recoverableProps)
24+
) {
25+
for (const recoverableProp of this.props.recoverableProps) {
26+
if (prevProps[recoverableProp] !== this.props[recoverableProp]) {
27+
this.setError(null);
28+
break;
29+
}
30+
}
31+
}
32+
}
2133

22-
return ref.current;
23-
}
34+
public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
35+
this.setError(error, errorInfo.componentStack);
36+
this.handleError(error, errorInfo);
37+
}
2438

25-
export const ErrorBoundary: React.FC<ErrorBoundaryProps<GenericProps> & GenericProps> = props => {
26-
const context = React.useContext(ErrorBoundaryContext);
27-
const fallback = props.FallbackComponent || context.FallbackComponent || FallbackComponent;
28-
const ActualFallback = React.useMemo<FallbackRender>(() => wrapFallback(fallback), [fallback]);
39+
protected handleError(origError: Error, errorInfo: React.ErrorInfo | null) {
40+
const error = new GenericReactErrorBoundaryError(origError, errorInfo);
2941

30-
const boundaryRef = React.useRef<SentryErrorBoundary | null>(null);
31-
const prevProps = usePrev(props);
42+
if (this.props.reportErrors !== false) {
43+
this.context.reporter.error(error);
44+
}
3245

33-
React.useEffect(() => {
34-
const boundary = boundaryRef.current;
35-
if (!boundary || !prevProps || !boundary.state.error || !props.recoverableProps) return;
46+
if (typeof this.props.onError === 'function') {
47+
this.props.onError(error);
48+
}
49+
}
3650

37-
for (const recoverableProp of props.recoverableProps) {
38-
if (prevProps[recoverableProp] !== props[recoverableProp]) {
39-
boundary.resetErrorBoundary();
40-
}
51+
public throwError = (error: Error) => {
52+
this.setError(error);
53+
this.handleError(error, null);
54+
};
55+
56+
protected setError(error: Error | null, componentStack: string | null = null) {
57+
this.setState({ error, componentStack });
58+
}
59+
60+
protected recover = () => {
61+
if (this.state.error !== null) {
62+
this.setError(null);
63+
}
64+
};
65+
66+
public render() {
67+
const {
68+
props: { FallbackComponent: Fallback = this.context.FallbackComponent || FallbackComponent, children },
69+
state: { error, componentStack },
70+
} = this;
71+
72+
if (error !== null) {
73+
return <Fallback error={error} componentStack={componentStack} tryRecovering={this.recover} />;
4174
}
42-
}, [props]);
43-
44-
return (
45-
<SentryErrorBoundary ref={boundaryRef} showDialog={false} fallback={ActualFallback} onError={props.onError}>
46-
{props.children}
47-
</SentryErrorBoundary>
48-
);
49-
};
75+
76+
return children;
77+
}
78+
}

src/ErrorBoundaryProvider.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ import * as React from 'react';
22
import { FallbackComponent } from './FallbackComponent';
33
import { FallbackProps } from './types';
44

5+
type ReportingAPI = {
6+
error(ex: Error): void;
7+
};
8+
59
export type ErrorBoundaryContext = {
10+
reporter: ReportingAPI;
611
FallbackComponent?: React.ElementType<FallbackProps>;
712
};
813

914
export const ErrorBoundaryContext = React.createContext<ErrorBoundaryContext>({
15+
reporter: console,
1016
FallbackComponent,
1117
});
1218

src/__tests__/ErrorBoundary.test.tsx

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* tslint:disable:jsx-wrap-multiline */
2-
import * as Sentry from '@sentry/react';
32
import { mount } from 'enzyme';
43
import * as React from 'react';
54

5+
import { GenericReactErrorBoundaryError } from '../error';
66
import { ErrorBoundary } from '../ErrorBoundary';
77
import { ErrorBoundaryProvider } from '../ErrorBoundaryProvider';
88
import { FallbackComponent } from '../FallbackComponent';
@@ -20,12 +20,22 @@ describe('ErrorBoundary component', () => {
2020
return <span>{String(value)}</span>;
2121
};
2222

23+
let reporter: { error: jest.MockedFunction<typeof console.error> };
24+
25+
beforeEach(() => {
26+
reporter = {
27+
error: jest.fn(),
28+
};
29+
});
30+
2331
describe('when exception is not thrown', () => {
2432
it('renders children', () => {
2533
const wrapper = mount(
26-
<ErrorBoundary>
27-
<TestComponent value="test" />
28-
</ErrorBoundary>,
34+
<ErrorBoundaryProvider reporter={reporter}>
35+
<ErrorBoundary>
36+
<TestComponent value="test" />
37+
</ErrorBoundary>
38+
</ErrorBoundaryProvider>,
2939
);
3040

3141
expect(wrapper.find(TestComponent)).toHaveHTML('<span>test</span>');
@@ -37,32 +47,69 @@ describe('ErrorBoundary component', () => {
3747
describe('when exception is thrown', () => {
3848
it('renders fallback component and passes error-related props', () => {
3949
const wrapper = mount(
40-
<ErrorBoundary>
41-
<TestComponent value={0} />
42-
</ErrorBoundary>,
50+
<ErrorBoundaryProvider reporter={reporter}>
51+
<ErrorBoundary>
52+
<TestComponent value={0} />
53+
</ErrorBoundary>
54+
</ErrorBoundaryProvider>,
4355
);
4456

4557
expect(wrapper.find(FallbackComponent)).toExist();
4658
expect(wrapper.find(FallbackComponent)).toHaveProp({
4759
error: ex,
4860
componentStack: expect.stringContaining('in TestComponent'),
49-
tryRecovering: (wrapper.find(Sentry.ErrorBoundary).instance() as any).resetErrorBoundary,
61+
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
5062
});
5163

5264
wrapper.unmount();
5365
});
5466

55-
it('calls onError prop', () => {
56-
const onError = jest.fn();
57-
const wrapper = mount(
58-
<ErrorBoundary onError={onError}>
59-
<TestComponent value={0} />
60-
</ErrorBoundary>,
61-
);
67+
describe('error reporting', () => {
68+
it('calls onError prop', () => {
69+
const onError = jest.fn();
70+
const wrapper = mount(
71+
<ErrorBoundaryProvider reporter={reporter}>
72+
<ErrorBoundary onError={onError}>
73+
<TestComponent value={0} />
74+
</ErrorBoundary>
75+
</ErrorBoundaryProvider>,
76+
);
6277

63-
expect(onError).toBeCalledWith(ex, expect.stringContaining('in TestComponent'), expect.any(String));
78+
expect(onError).toBeCalledWith(new GenericReactErrorBoundaryError(ex, null));
6479

65-
wrapper.unmount();
80+
expect(onError.mock.calls[0][0]).toHaveProperty('componentStack', expect.stringContaining('in TestComponent'));
81+
82+
wrapper.unmount();
83+
});
84+
85+
it('reports errors by default', () => {
86+
const wrapper = mount(
87+
<ErrorBoundaryProvider reporter={reporter}>
88+
<ErrorBoundary>
89+
<TestComponent value={0} />
90+
</ErrorBoundary>
91+
</ErrorBoundaryProvider>,
92+
);
93+
94+
expect(reporter.error).toBeCalledWith(new GenericReactErrorBoundaryError(ex, null));
95+
expect(reporter.error.mock.calls[0][0]).toHaveProperty('componentStack', expect.any(String));
96+
97+
wrapper.unmount();
98+
});
99+
100+
it('does reports error if reporting is disabled', () => {
101+
const wrapper = mount(
102+
<ErrorBoundaryProvider reporter={reporter}>
103+
<ErrorBoundary reportErrors={false}>
104+
<TestComponent value={0} />
105+
</ErrorBoundary>
106+
</ErrorBoundaryProvider>,
107+
);
108+
109+
expect(reporter.error).not.toBeCalled();
110+
111+
wrapper.unmount();
112+
});
66113
});
67114

68115
describe('and a custom fallback component is provided', () => {
@@ -71,17 +118,19 @@ describe('ErrorBoundary component', () => {
71118
const CustomFallbackComponent = () => <div>foo</div>;
72119

73120
const wrapper = mount(
74-
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
75-
<TestComponent value={0} />
76-
</ErrorBoundary>,
121+
<ErrorBoundaryProvider reporter={reporter}>
122+
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
123+
<TestComponent value={0} />
124+
</ErrorBoundary>
125+
</ErrorBoundaryProvider>,
77126
);
78127

79128
expect(wrapper.find(FallbackComponent)).not.toExist(); // makes sure we don't render the original one
80129
expect(wrapper.find(CustomFallbackComponent)).toExist();
81130
expect(wrapper.find(CustomFallbackComponent)).toHaveProp({
82131
error: ex,
83132
componentStack: expect.stringContaining('in TestComponent'),
84-
tryRecovering: (wrapper.find(Sentry.ErrorBoundary).instance() as any).resetErrorBoundary,
133+
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
85134
});
86135

87136
wrapper.unmount();
@@ -93,7 +142,7 @@ describe('ErrorBoundary component', () => {
93142
const CustomFallbackComponent = () => <div>foo</div>;
94143

95144
const wrapper = mount(
96-
<ErrorBoundaryProvider FallbackComponent={CustomFallbackComponent}>
145+
<ErrorBoundaryProvider reporter={reporter} FallbackComponent={CustomFallbackComponent}>
97146
>
98147
<ErrorBoundary>
99148
<TestComponent value={0} />
@@ -106,7 +155,7 @@ describe('ErrorBoundary component', () => {
106155
expect(wrapper.find(CustomFallbackComponent)).toHaveProp({
107156
error: ex,
108157
componentStack: expect.stringContaining('in TestComponent'),
109-
tryRecovering: (wrapper.find(Sentry.ErrorBoundary).instance() as any).resetErrorBoundary,
158+
tryRecovering: (wrapper.find(ErrorBoundary).instance() as any).recover,
110159
});
111160

112161
wrapper.unmount();
@@ -119,7 +168,7 @@ describe('ErrorBoundary component', () => {
119168
const CustomFallbackPropsComponent = () => <div>level!</div>;
120169

121170
const wrapper = mount(
122-
<ErrorBoundaryProvider FallbackComponent={CustomFallbackContextComponent}>
171+
<ErrorBoundaryProvider reporter={reporter} FallbackComponent={CustomFallbackContextComponent}>
123172
<ErrorBoundary FallbackComponent={CustomFallbackPropsComponent}>
124173
<TestComponent value={0} />
125174
</ErrorBoundary>
@@ -139,9 +188,11 @@ describe('ErrorBoundary component', () => {
139188
const getValue = jest.fn().mockReturnValue(0);
140189

141190
const wrapper = mount(
142-
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
143-
<TestComponent getValue={getValue} />
144-
</ErrorBoundary>,
191+
<ErrorBoundaryProvider reporter={reporter}>
192+
<ErrorBoundary FallbackComponent={CustomFallbackComponent}>
193+
<TestComponent getValue={getValue} />
194+
</ErrorBoundary>
195+
</ErrorBoundaryProvider>,
145196
);
146197

147198
expect(wrapper.find(CustomFallbackComponent)).toExist();

0 commit comments

Comments
 (0)