Skip to content

Commit a6cee2c

Browse files
committed
Add Error Boundary
1 parent a200743 commit a6cee2c

File tree

3 files changed

+130
-37
lines changed

3 files changed

+130
-37
lines changed

js/views/reusable/LinkButton.jsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,52 @@ class LinkButton extends React.Component {
1313
onClick: PropTypes.func,
1414
color: PropTypes.string,
1515
size: PropTypes.string,
16+
target: PropTypes.string,
17+
}
18+
19+
static defaultProps = {
20+
target: '_blank',
1621
}
1722

1823
triggerLink = () => {
19-
if (this.props.href.split(':')[0] === 'mailto') {
20-
window.location = this.props.href
24+
const { href } = this.props
25+
if (href.split(':')[0] === 'mailto') {
26+
window.location = href
2127
} else {
22-
window.open(this.props.href)
28+
window.open(href)
2329
}
2430
}
2531

2632
render() {
33+
const { href, target, size, color, label, onClick } = this.props
2734
let wrapperStyle = null
2835
let textStyle = null
29-
if (this.props.size === 'small') {
36+
if (size === 'small') {
3037
wrapperStyle =
31-
this.props.color === 'secondary'
38+
color === 'secondary'
3239
? [styles.wrapper, styles.wrapperSecondary, styles.wrapperSmall]
3340
: [styles.wrapper, styles.wrapperSmall]
3441
textStyle =
35-
this.props.color === 'secondary'
42+
color === 'secondary'
3643
? [styles.text, styles.textSecondary, styles.textSmall]
3744
: [styles.text, styles.textSmall]
3845
} else {
3946
wrapperStyle =
40-
this.props.color === 'secondary'
47+
color === 'secondary'
4148
? [styles.wrapper, styles.wrapperSecondary]
4249
: styles.wrapper
4350
textStyle =
44-
this.props.color === 'secondary'
51+
color === 'secondary'
4552
? [styles.text, styles.textSecondary]
4653
: styles.text
4754
}
4855

4956
const inner = (
5057
<View style={wrapperStyle}>
51-
<Text style={textStyle}>{this.props.label}</Text>
58+
<Text style={textStyle}>{label}</Text>
5259
</View>
5360
)
54-
if (this.props.href && iOS.detect()) {
61+
if (href && iOS.detect()) {
5562
return (
5663
<TouchableOpacity
5764
iOSHacks
@@ -62,21 +69,19 @@ class LinkButton extends React.Component {
6269
</TouchableOpacity>
6370
)
6471
}
65-
if (this.props.href) {
72+
if (href) {
6673
return (
6774
<TouchableOpacity
6875
activeOpacity={75}
69-
target="_blank"
76+
target={target}
7077
accessibilityRole="link"
71-
href={this.props.href}
78+
href={href}
7279
>
7380
{inner}
7481
</TouchableOpacity>
7582
)
7683
}
77-
return (
78-
<TouchableOpacity onClick={this.props.onClick}>{inner}</TouchableOpacity>
79-
)
84+
return <TouchableOpacity onClick={onClick}>{inner}</TouchableOpacity>
8085
}
8186
}
8287
styles = StyleSheet.create({

js/views/shell/Content.jsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { View, StyleSheet } from 'react-native'
44
import { withRouter, Route } from 'react-router-dom'
55

66
import Switch from './Switch.jsx'
7+
import { ErrorBoundary } from './ErrorBoundary.jsx'
78

89
import Events from '../../stores/Events'
910
import Station from '../station/Station.jsx'
@@ -75,27 +76,40 @@ class Content extends React.Component {
7576
}
7677
onLayout={this.triggerLayout}
7778
>
78-
<Switch location={location} key="switch" timeout={400}>
79-
<Route path="/" exact render={wrapFn(rootComponent)} />
80-
<Route path="/s/:region/:station" exact render={wrapFn(Station)} />
81-
<Route path="/s/:region/:station/save" exact render={wrapFn(Save)} />
82-
<Route path="/l/:region" exact render={wrapFn(LineList)} />
83-
<Route path="/l/:region/all" exact render={wrapFn(AllLines)} />
84-
<Route
85-
path="/l/:region/:agency_id/:route_short_name"
86-
exact
87-
render={wrapFn(Line)}
88-
/>
89-
<Route
90-
path="/l/:region/:agency_id/:route_short_name/picker"
91-
exact
92-
render={wrapFn(LinePicker)}
93-
/>
94-
<Route path="/sponsor" exact render={wrapFn(Sponsor)} />
95-
<Route path="/region" exact render={wrapFn(Region)} />
96-
<Route path="/settings" exact render={wrapFn(Settings)} />
97-
<Route render={wrapFn(NoMatch)} />
98-
</Switch>
79+
<ErrorBoundary>
80+
<Switch location={location} key="switch" timeout={400}>
81+
<Route path="/" exact render={wrapFn(rootComponent)} />
82+
<Route
83+
path="/fail"
84+
exact
85+
render={() => {
86+
throw new Error('Intentional error')
87+
}}
88+
/>
89+
<Route path="/s/:region/:station" exact render={wrapFn(Station)} />
90+
<Route
91+
path="/s/:region/:station/save"
92+
exact
93+
render={wrapFn(Save)}
94+
/>
95+
<Route path="/l/:region" exact render={wrapFn(LineList)} />
96+
<Route path="/l/:region/all" exact render={wrapFn(AllLines)} />
97+
<Route
98+
path="/l/:region/:agency_id/:route_short_name"
99+
exact
100+
render={wrapFn(Line)}
101+
/>
102+
<Route
103+
path="/l/:region/:agency_id/:route_short_name/picker"
104+
exact
105+
render={wrapFn(LinePicker)}
106+
/>
107+
<Route path="/sponsor" exact render={wrapFn(Sponsor)} />
108+
<Route path="/region" exact render={wrapFn(Region)} />
109+
<Route path="/settings" exact render={wrapFn(Settings)} />
110+
<Route render={wrapFn(NoMatch)} />
111+
</Switch>
112+
</ErrorBoundary>
99113
</View>
100114
)
101115
}

js/views/shell/ErrorBoundary.jsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { Component } from 'react'
2+
import { View, Text, StyleSheet } from 'react-native'
3+
4+
import { vars } from '../../styles.js'
5+
import Wrapper from './Wrapper.jsx'
6+
import Header from '../reusable/Header.jsx'
7+
import LinkButton from '../reusable/LinkButton.jsx'
8+
9+
let styles
10+
11+
export class ErrorBoundary extends Component {
12+
constructor(props) {
13+
super(props)
14+
this.state = { hasError: false }
15+
}
16+
17+
static getDerivedStateFromError() {
18+
return { hasError: true }
19+
}
20+
21+
componentDidCatch(error, errorInfo) {
22+
// TODO: Log error to an error reporting service
23+
console.error(error, errorInfo)
24+
}
25+
26+
render() {
27+
const { hasError } = this.state
28+
if (hasError) {
29+
// You can render any custom fallback UI
30+
return (
31+
<Wrapper>
32+
<View style={styles.wrapper}>
33+
<Header title="Error!" hideClose />
34+
<View style={styles.error}>
35+
<Text style={styles.errorMessage}>
36+
There was an unexpected error. You can either try reload this
37+
page, or return home.
38+
</Text>
39+
<LinkButton
40+
href={window.location.toString()}
41+
target="_self"
42+
label="Reload Page"
43+
/>
44+
<LinkButton
45+
color="secondary"
46+
href="/"
47+
label="Return Home"
48+
target="_self"
49+
/>
50+
</View>
51+
</View>
52+
</Wrapper>
53+
)
54+
}
55+
56+
const { children } = this.props
57+
return children
58+
}
59+
}
60+
61+
const { padding, defaultFontSize, fontFamily } = vars
62+
styles = StyleSheet.create({
63+
wrapper: {
64+
flex: 1,
65+
},
66+
error: {
67+
padding,
68+
},
69+
errorMessage: {
70+
fontSize: defaultFontSize,
71+
fontFamily,
72+
marginBottom: padding,
73+
},
74+
})

0 commit comments

Comments
 (0)