-
-
Notifications
You must be signed in to change notification settings - Fork 4k
/
errorBoundary.tsx
122 lines (100 loc) · 2.86 KB
/
errorBoundary.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import {browserHistory} from 'react-router';
import PropTypes from 'prop-types';
import React from 'react';
import styled from '@emotion/styled';
import * as Sentry from '@sentry/react';
import {t} from 'app/locale';
import Alert from 'app/components/alert';
import DetailedError from 'app/components/errors/detailedError';
type DefaultProps = {
mini: boolean;
};
type Props = DefaultProps & {
message?: React.ReactNode;
customComponent?: React.ReactNode;
className?: string;
};
type State = {
error: Error | null;
};
const exclamation = ['Raspberries', 'Snap', 'Frig', 'Welp', 'Uhhhh', 'Hmmm'] as const;
function getExclamation() {
return exclamation[Math.floor(Math.random() * exclamation.length)];
}
class ErrorBoundary extends React.Component<Props, State> {
static propTypes = {
mini: PropTypes.bool,
message: PropTypes.node,
customComponent: PropTypes.node,
};
static defaultProps: DefaultProps = {
mini: false,
};
state: State = {
error: null,
};
componentDidMount() {
// Listen for route changes so we can clear error
this.unlistenBrowserHistory = browserHistory.listen(() =>
this.setState({error: null})
);
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.setState({error});
Sentry.withScope(scope => {
scope.setExtra('errorInfo', errorInfo);
Sentry.captureException(error);
});
}
componentWillUnmount() {
if (this.unlistenBrowserHistory) {
this.unlistenBrowserHistory();
}
}
// XXX: browserHistory.listen does not have a correct return type.
unlistenBrowserHistory: any;
render() {
const {error} = this.state;
if (!error) {
//when there's not an error, render children untouched
return this.props.children;
}
const {customComponent, mini, message, className} = this.props;
if (customComponent) {
return customComponent;
}
if (mini) {
return (
<Alert type="error" icon="icon-circle-exclamation" className={className}>
{message || t('There was a problem rendering this component')}
</Alert>
);
}
return (
<Wrapper>
<DetailedError
heading={getExclamation()}
message={t(
`Something went horribly wrong rendering this page.
We use a decent error reporting service so this will probably be fixed soon. Unless our error reporting service is also broken. That would be awkward.
Anyway, we apologize for the inconvenience.`
)}
/>
<StackTrace>{error.toString()}</StackTrace>
</Wrapper>
);
}
}
const Wrapper = styled('div')`
color: ${p => p.theme.gray700};
padding: ${p => p.theme.grid * 3}px;
max-width: 1000px;
margin: auto;
`;
const StackTrace = styled('pre')`
white-space: pre-wrap;
margin: 32px;
margin-left: 85px;
margin-right: 18px;
`;
export default ErrorBoundary;