Skip to content

Commit

Permalink
✨ Add ErrorBoundary component (#2)
Browse files Browse the repository at this point in the history
* ✨ Add module export file
* ✨ Add ErrorBoundary component
* ✅ Add test suite
* 🔧 Upgrade peerDependencies version
* 🔧 Update flowconfig
* 📝 Write docs
  • Loading branch information
carloscuesta committed Feb 10, 2019
1 parent 08df796 commit cdd5d47
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 12 deletions.
14 changes: 4 additions & 10 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
[ignore]
[untyped]
.*/node_modules/react-native/Libraries/react-native/react-native-implementation.js

[include]

[libs]

[lints]

[options]

[strict]
[version]
0.92.1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
*.log
coverage
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,102 @@
# react-native-error-boundary

[![Travis Build Status](https://img.shields.io/travis/carloscuesta/gitmoji-cli.svg?style=flat-square)](https://travis-ci.com/carloscuesta/react-native-error-boundary)
[![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)
[![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)
[![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)
[![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)

> A simple and reusable React-Native error boundary component 🐛
## Install

```bash
$ yarn add react-native-error-boundary
```

## Usage

Using this component is really simple. First you have to import the `ErrorBoundary`
component. Then, you have to **wrap** it **around any component** that
**could throw** an **error**.

### Basic

```jsx
import ErrorBoundary from 'react-native-error-boundary'

const App = () => (
<ErrorBoundary>
<ChildrenThatCouldThrowEror />
</ErrorBoundary>
)
```

### Logging errors

You can **log the error** by providing an `onError` function to the component.

```jsx
import ErrorBoundary from 'react-native-error-boundary'

const errorHandler = (error: Error, stackTrace: string) => {
/* Log the error to an error reporting service */
}

const App = () => (
<ErrorBoundary onError={errorHandler}>
<ChildrenThatCouldThrowEror />
</ErrorBoundary>
)
```

### Custom fallback component

You can customize the appearance of the fallback component by providing the `FallbackComponent` prop.

```jsx
import ErrorBoundary from 'react-native-error-boundary'

const CustomFallback = (props: { error: Error, resetError: Function }) => (
<View>
<Text>Something happened!</Text>
<Text>{props.error.toString()}</Text>
<Button onPress={props.resetError} title={'Try again'} />
</View>
)

const App = () => (
<ErrorBoundary FallbackComponent={CustomFallback}>
<ChildrenThatCouldThrowEror></ChildrenThatCouldThrowEror>
</ErrorBoundary>
)
```

## API

### `ErrorBoundary`

These are the props that the `ErrorBoundary` component accepts:

| Property | Type | Required | Default | Description |
|-------------------|-------------------|----------|---------------------|------------------------------------|
| children | `React.Children` | `true` | | Components that may throw an error |
| FallbackComponent | `React.Component` | `false` | `FallbackComponent` | UI rendered when there's an error |
| onError | `Function` | `false` | | Function for logging the error |

### `FallbackComponent`

These are the props that the `FallbackComponent` **receives**:

| Property | Type | Default | Description |
|------------|------------|---------|-------------------------------------|
| error | `Error` | | The thrown error |
| resetError | `Function` | | A function to reset the error state |

## Demo

<img
src='https://user-images.githubusercontent.com/7629661/52532748-d8758400-2d29-11e9-80a0-15182517271c.gif'
alt='react-native-error-boundary'
width='358px'
/>
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
},
"jest": {
"preset": "react-native",
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.js"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
Expand All @@ -46,8 +50,8 @@
},
"homepage": "https://github.com/carloscuesta/react-native-error-boundary",
"peerDependencies": {
"react": ">=16.0.0",
"react-native": ">=0.55.0"
"react": ">=16.6.0",
"react-native": ">=0.57.7"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
Expand All @@ -58,6 +62,7 @@
"jest": "^24.1.0",
"react": "16.6.3",
"react-native": "^0.58.4",
"react-test-renderer": "^16.8.1",
"snazzy": "^8.0.0",
"standard": "^12.0.1"
}
Expand Down
27 changes: 27 additions & 0 deletions src/ErrorBoundary/FallbackComponent/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @flow
import React from 'react'
import {
SafeAreaView,
Text,
TouchableOpacity,
View
} from 'react-native'

import styles from './styles'

type Props = { error: Error, resetError: Function }

const FallbackComponent = (props: Props) => (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>{'Oops!'}</Text>
<Text style={styles.subtitle}>{'There\'s an error'}</Text>
<Text style={styles.error}>{props.error.toString()}</Text>
<TouchableOpacity style={styles.button} onPress={props.resetError}>
<Text style={styles.buttonText}>{'Try again'}</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
)

export default FallbackComponent
35 changes: 35 additions & 0 deletions src/ErrorBoundary/FallbackComponent/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @flow
import { StyleSheet } from 'react-native'

export default StyleSheet.create({
container: {
backgroundColor: '#fafafa',
flex: 1,
justifyContent: 'center'
},
content: {
marginHorizontal: 16
},
title: {
fontSize: 48,
fontWeight: '300',
paddingBottom: 16
},
subtitle: {
fontSize: 32,
fontWeight: '800'
},
error: {
paddingVertical: 16
},
button: {
backgroundColor: '#2196f3',
borderRadius: 50,
padding: 16
},
buttonText: {
color: '#fff',
fontWeight: '600',
textAlign: 'center'
}
})
47 changes: 47 additions & 0 deletions src/ErrorBoundary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @flow
import React, { type Node, type ComponentType } from 'react'

import FallbackComponent from './FallbackComponent'

type Props = {
children: Node,
FallbackComponent: ComponentType<any>,
onError?: Function
}

type State = { error: Error | null, hasError: boolean }

class ErrorBoundary extends React.Component<Props, State> {
state = { error: null, hasError: false }

static defaultProps = {
FallbackComponent: FallbackComponent
}

static getDerivedStateFromError (error: Error) {
return { error, hasError: true }
}

componentDidCatch (error: Error, info: { componentStack: string }) {
if (typeof this.props.onError === 'function') {
this.props.onError.call(this, error, info.componentStack)
}
}

resetError: Function = () => {
this.setState({ error: null, hasError: false })
}

render () {
const { FallbackComponent } = this.props

return this.state.hasError
? <FallbackComponent
error={this.state.error}
resetError={this.resetError}
/>
: this.props.children
}
}

export default ErrorBoundary
98 changes: 98 additions & 0 deletions src/__tests__/__snapshots__/errorBoundary.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ErrorBoundary should catch errors set the state and render the default FallbackComponent 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"backgroundColor": "#fafafa",
"flex": 1,
"justifyContent": "center",
}
}
>
<View
style={
Object {
"marginHorizontal": 16,
}
}
>
<Text
style={
Object {
"fontSize": 48,
"fontWeight": "300",
"paddingBottom": 16,
}
}
>
Oops!
</Text>
<Text
style={
Object {
"fontSize": 32,
"fontWeight": "800",
}
}
>
There's an error
</Text>
<Text
style={
Object {
"paddingVertical": 16,
}
}
>
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.
in View (created by View)
in View (at errorBoundary.spec.js:41)
in ErrorBoundary (at errorBoundary.spec.js:40)
</Text>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"backgroundColor": "#2196f3",
"borderRadius": 50,
"opacity": 1,
"padding": 16,
}
}
>
<Text
style={
Object {
"color": "#fff",
"fontWeight": "600",
"textAlign": "center",
}
}
>
Try again
</Text>
</View>
</View>
</RCTSafeAreaView>
`;

exports[`ErrorBoundary should catch errors set the state render the props.FallbackComponent and call props.onError 1`] = `
<Text>
Error!
</Text>
`;

exports[`ErrorBoundary should render children when there are no errors 1`] = `
<Text>
Hey!
</Text>
`;
Loading

0 comments on commit cdd5d47

Please sign in to comment.