Skip to content

Commit cdd5d47

Browse files
committed
✨ Add ErrorBoundary component (#2)
* ✨ Add module export file * ✨ Add ErrorBoundary component * ✅ Add test suite * 🔧 Upgrade peerDependencies version * 🔧 Update flowconfig * 📝 Write docs
1 parent 08df796 commit cdd5d47

File tree

11 files changed

+420
-12
lines changed

11 files changed

+420
-12
lines changed

.flowconfig

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
[ignore]
1+
[untyped]
2+
.*/node_modules/react-native/Libraries/react-native/react-native-implementation.js
23

3-
[include]
4-
5-
[libs]
6-
7-
[lints]
8-
9-
[options]
10-
11-
[strict]
4+
[version]
5+
0.92.1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
22
*.log
3+
coverage

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,102 @@
11
# react-native-error-boundary
2+
3+
[![Travis Build Status](https://img.shields.io/travis/carloscuesta/gitmoji-cli.svg?style=flat-square)](https://travis-ci.com/carloscuesta/react-native-error-boundary)
4+
[![Codecov](https://img.shields.io/codecov/c/github/carloscuesta/react-native-error-boundary.svg?style=flat-square)](https://codecov.io/gh/carloscuesta/react-native-error-boundary)
5+
[![Codeclimate](https://img.shields.io/codeclimate/maintainability/carloscuesta/react-native-error-boundary.svg?style=flat-square)](https://codeclimate.com/github/carloscuesta/react-native-error-boundary)
6+
[![Npm Version](https://img.shields.io/npm/v/react-native-error-boundary.svg?style=flat-square)](https://www.npmjs.com/package/react-native-error-boundary)
7+
[![Npm Downloads](https://img.shields.io/npm/dt/react-native-error-boundary.svg?style=flat-square)](https://www.npmjs.com/package/react-native-error-boundary)
8+
9+
> A simple and reusable React-Native error boundary component 🐛
10+
11+
## Install
12+
13+
```bash
14+
$ yarn add react-native-error-boundary
15+
```
16+
17+
## Usage
18+
19+
Using this component is really simple. First you have to import the `ErrorBoundary`
20+
component. Then, you have to **wrap** it **around any component** that
21+
**could throw** an **error**.
22+
23+
### Basic
24+
25+
```jsx
26+
import ErrorBoundary from 'react-native-error-boundary'
27+
28+
const App = () => (
29+
<ErrorBoundary>
30+
<ChildrenThatCouldThrowEror />
31+
</ErrorBoundary>
32+
)
33+
```
34+
35+
### Logging errors
36+
37+
You can **log the error** by providing an `onError` function to the component.
38+
39+
```jsx
40+
import ErrorBoundary from 'react-native-error-boundary'
41+
42+
const errorHandler = (error: Error, stackTrace: string) => {
43+
/* Log the error to an error reporting service */
44+
}
45+
46+
const App = () => (
47+
<ErrorBoundary onError={errorHandler}>
48+
<ChildrenThatCouldThrowEror />
49+
</ErrorBoundary>
50+
)
51+
```
52+
53+
### Custom fallback component
54+
55+
You can customize the appearance of the fallback component by providing the `FallbackComponent` prop.
56+
57+
```jsx
58+
import ErrorBoundary from 'react-native-error-boundary'
59+
60+
const CustomFallback = (props: { error: Error, resetError: Function }) => (
61+
<View>
62+
<Text>Something happened!</Text>
63+
<Text>{props.error.toString()}</Text>
64+
<Button onPress={props.resetError} title={'Try again'} />
65+
</View>
66+
)
67+
68+
const App = () => (
69+
<ErrorBoundary FallbackComponent={CustomFallback}>
70+
<ChildrenThatCouldThrowEror></ChildrenThatCouldThrowEror>
71+
</ErrorBoundary>
72+
)
73+
```
74+
75+
## API
76+
77+
### `ErrorBoundary`
78+
79+
These are the props that the `ErrorBoundary` component accepts:
80+
81+
| Property | Type | Required | Default | Description |
82+
|-------------------|-------------------|----------|---------------------|------------------------------------|
83+
| children | `React.Children` | `true` | | Components that may throw an error |
84+
| FallbackComponent | `React.Component` | `false` | `FallbackComponent` | UI rendered when there's an error |
85+
| onError | `Function` | `false` | | Function for logging the error |
86+
87+
### `FallbackComponent`
88+
89+
These are the props that the `FallbackComponent` **receives**:
90+
91+
| Property | Type | Default | Description |
92+
|------------|------------|---------|-------------------------------------|
93+
| error | `Error` | | The thrown error |
94+
| resetError | `Function` | | A function to reset the error state |
95+
96+
## Demo
97+
98+
<img
99+
src='https://user-images.githubusercontent.com/7629661/52532748-d8758400-2d29-11e9-80a0-15182517271c.gif'
100+
alt='react-native-error-boundary'
101+
width='358px'
102+
/>

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
},
2525
"jest": {
2626
"preset": "react-native",
27+
"collectCoverage": true,
28+
"collectCoverageFrom": [
29+
"src/**/*.js"
30+
],
2731
"transform": {
2832
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
2933
}
@@ -46,8 +50,8 @@
4650
},
4751
"homepage": "https://github.com/carloscuesta/react-native-error-boundary",
4852
"peerDependencies": {
49-
"react": ">=16.0.0",
50-
"react-native": ">=0.55.0"
53+
"react": ">=16.6.0",
54+
"react-native": ">=0.57.7"
5155
},
5256
"devDependencies": {
5357
"babel-eslint": "^10.0.1",
@@ -58,6 +62,7 @@
5862
"jest": "^24.1.0",
5963
"react": "16.6.3",
6064
"react-native": "^0.58.4",
65+
"react-test-renderer": "^16.8.1",
6166
"snazzy": "^8.0.0",
6267
"standard": "^12.0.1"
6368
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// @flow
2+
import React from 'react'
3+
import {
4+
SafeAreaView,
5+
Text,
6+
TouchableOpacity,
7+
View
8+
} from 'react-native'
9+
10+
import styles from './styles'
11+
12+
type Props = { error: Error, resetError: Function }
13+
14+
const FallbackComponent = (props: Props) => (
15+
<SafeAreaView style={styles.container}>
16+
<View style={styles.content}>
17+
<Text style={styles.title}>{'Oops!'}</Text>
18+
<Text style={styles.subtitle}>{'There\'s an error'}</Text>
19+
<Text style={styles.error}>{props.error.toString()}</Text>
20+
<TouchableOpacity style={styles.button} onPress={props.resetError}>
21+
<Text style={styles.buttonText}>{'Try again'}</Text>
22+
</TouchableOpacity>
23+
</View>
24+
</SafeAreaView>
25+
)
26+
27+
export default FallbackComponent
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @flow
2+
import { StyleSheet } from 'react-native'
3+
4+
export default StyleSheet.create({
5+
container: {
6+
backgroundColor: '#fafafa',
7+
flex: 1,
8+
justifyContent: 'center'
9+
},
10+
content: {
11+
marginHorizontal: 16
12+
},
13+
title: {
14+
fontSize: 48,
15+
fontWeight: '300',
16+
paddingBottom: 16
17+
},
18+
subtitle: {
19+
fontSize: 32,
20+
fontWeight: '800'
21+
},
22+
error: {
23+
paddingVertical: 16
24+
},
25+
button: {
26+
backgroundColor: '#2196f3',
27+
borderRadius: 50,
28+
padding: 16
29+
},
30+
buttonText: {
31+
color: '#fff',
32+
fontWeight: '600',
33+
textAlign: 'center'
34+
}
35+
})

src/ErrorBoundary/index.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @flow
2+
import React, { type Node, type ComponentType } from 'react'
3+
4+
import FallbackComponent from './FallbackComponent'
5+
6+
type Props = {
7+
children: Node,
8+
FallbackComponent: ComponentType<any>,
9+
onError?: Function
10+
}
11+
12+
type State = { error: Error | null, hasError: boolean }
13+
14+
class ErrorBoundary extends React.Component<Props, State> {
15+
state = { error: null, hasError: false }
16+
17+
static defaultProps = {
18+
FallbackComponent: FallbackComponent
19+
}
20+
21+
static getDerivedStateFromError (error: Error) {
22+
return { error, hasError: true }
23+
}
24+
25+
componentDidCatch (error: Error, info: { componentStack: string }) {
26+
if (typeof this.props.onError === 'function') {
27+
this.props.onError.call(this, error, info.componentStack)
28+
}
29+
}
30+
31+
resetError: Function = () => {
32+
this.setState({ error: null, hasError: false })
33+
}
34+
35+
render () {
36+
const { FallbackComponent } = this.props
37+
38+
return this.state.hasError
39+
? <FallbackComponent
40+
error={this.state.error}
41+
resetError={this.resetError}
42+
/>
43+
: this.props.children
44+
}
45+
}
46+
47+
export default ErrorBoundary
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ErrorBoundary should catch errors set the state and render the default FallbackComponent 1`] = `
4+
<RCTSafeAreaView
5+
emulateUnlessSupported={true}
6+
style={
7+
Object {
8+
"backgroundColor": "#fafafa",
9+
"flex": 1,
10+
"justifyContent": "center",
11+
}
12+
}
13+
>
14+
<View
15+
style={
16+
Object {
17+
"marginHorizontal": 16,
18+
}
19+
}
20+
>
21+
<Text
22+
style={
23+
Object {
24+
"fontSize": 48,
25+
"fontWeight": "300",
26+
"paddingBottom": 16,
27+
}
28+
}
29+
>
30+
Oops!
31+
</Text>
32+
<Text
33+
style={
34+
Object {
35+
"fontSize": 32,
36+
"fontWeight": "800",
37+
}
38+
}
39+
>
40+
There's an error
41+
</Text>
42+
<Text
43+
style={
44+
Object {
45+
"paddingVertical": 16,
46+
}
47+
}
48+
>
49+
Invariant Violation: Objects are not valid as a React child (found: Error: This is a test error!). If you meant to render a collection of children, use an array instead.
50+
in View (created by View)
51+
in View (at errorBoundary.spec.js:41)
52+
in ErrorBoundary (at errorBoundary.spec.js:40)
53+
</Text>
54+
<View
55+
accessible={true}
56+
isTVSelectable={true}
57+
onResponderGrant={[Function]}
58+
onResponderMove={[Function]}
59+
onResponderRelease={[Function]}
60+
onResponderTerminate={[Function]}
61+
onResponderTerminationRequest={[Function]}
62+
onStartShouldSetResponder={[Function]}
63+
style={
64+
Object {
65+
"backgroundColor": "#2196f3",
66+
"borderRadius": 50,
67+
"opacity": 1,
68+
"padding": 16,
69+
}
70+
}
71+
>
72+
<Text
73+
style={
74+
Object {
75+
"color": "#fff",
76+
"fontWeight": "600",
77+
"textAlign": "center",
78+
}
79+
}
80+
>
81+
Try again
82+
</Text>
83+
</View>
84+
</View>
85+
</RCTSafeAreaView>
86+
`;
87+
88+
exports[`ErrorBoundary should catch errors set the state render the props.FallbackComponent and call props.onError 1`] = `
89+
<Text>
90+
Error!
91+
</Text>
92+
`;
93+
94+
exports[`ErrorBoundary should render children when there are no errors 1`] = `
95+
<Text>
96+
Hey!
97+
</Text>
98+
`;

0 commit comments

Comments
 (0)