├── .eslintrc.js
├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── src
├── errorBoundary
│ ├── ErrorBoundary.tsx
│ └── withErrorBoundary.tsx
├── globalError
│ └── index.ts
├── index.ts
└── promiseTracker
│ └── index.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | ecmaVersion: 7,
5 | sourceType: "module",
6 | ecmaFeatures: {
7 | tsx: true
8 | }
9 | },
10 | plugins: ['@typescript-eslint'],
11 | rules: {
12 | 'no-var': "error",
13 | '@typescript-eslint/consistent-type-definitions': [
14 | "error",
15 | "interface"
16 | ]
17 | },
18 | }
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Enable version updates for npm
4 | - package-ecosystem: 'npm'
5 | # Look for `package.json` and `lock` files in the `root` directory
6 | directory: '/'
7 | # Check the npm registry for updates every day (weekdays)
8 | schedule:
9 | interval: 'daily'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # build
11 | dist/
12 |
13 | # dependencies
14 | node_modules/
15 |
16 | yarn.lock
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 iChengbo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-error-helper
2 |
3 | > A helper for React Native to catch global JS errors and provide some ways to resolve error boundaries.
4 |
5 |
6 | [](./LICENSE)
7 | [](https://www.npmjs.com/package/react-native-error-helper)
8 | [](https://www.npmjs.com/package/react-native-error-helper)
9 | [](https://github.com/iChengbo/react-native-error-helper)
10 |
11 |
12 |
13 |
14 |
15 | ## Table of Contents
16 | - [Install](#Install)
17 | - [Usage](#Usage)
18 | - [setGlobalErrorHandler](#setGlobalErrorHandler)
19 | - [setPromiseUnCatchHandler](#setPromiseUnCatchHandler)
20 | - [ErrorBoundary](#ErrorBoundary)
21 | - [withErrorBoundary](#withErrorBoundary)
22 | - [LICENSE](#LICENSE)
23 |
24 |
25 | ## Install
26 |
27 | > yarn add react-native-error-helper
28 |
29 | ## Usage
30 |
31 | ### setGlobalErrorHandler
32 |
33 | ```js
34 | import { setGlobalErrorHandler } from 'react-native-error-helper';
35 |
36 | setGlobalErrorHandler((error, isFatal) => {
37 | console.log('global error:', error, isFatal);
38 | }, true);
39 | ```
40 |
41 | ### setPromiseUnCatchHandler
42 |
43 | ```js
44 | import { setPromiseUnCatchHandler } from 'react-native-error-helper';
45 |
46 | setPromiseUnCatchHandler((id, err) => {
47 | console.log('promise un catch:', err);
48 | }, true);
49 | ```
50 |
51 | ### ErrorBoundary
52 |
53 | ```js
54 | import { ErrorBoundary } from 'react-native-error-helper';
55 |
56 | const App = () => (
57 |
58 |
59 |
60 | )
61 | ```
62 |
63 | ### withErrorBoundary
64 |
65 | #### class component
66 |
67 | ```js
68 | import { withErrorBoundary } from 'react-native-error-helper';
69 |
70 | @withErrorBoundary({
71 | renderBoundary: ({error}) => {
72 | return catch error: {error.message};
73 | },
74 | })
75 | class BugCenter extends React.Component {
76 | constructor(props) {
77 | super(props);
78 | this.state = {
79 | isError: false,
80 | };
81 | }
82 |
83 | render() {
84 | const {isError} = this.state;
85 | if (isError) {
86 | throw new Error('💥');
87 | } else {
88 | return (
89 | {
91 | this.setState({
92 | isError: true
93 | });
94 | }}>
95 | {String(isError)}
96 |
97 | );
98 | }
99 | }
100 | }
101 | ```
102 | #### function component
103 |
104 | ```js
105 | import { withErrorBoundary } from 'react-native-error-helper';
106 |
107 | const BugCenter = props => {
108 | const [isError, setIsError] = useState();
109 | if (isError) {
110 | throw new Error('💥');
111 | } else {
112 | return (
113 | {
115 | this.setState({
116 | isError: true
117 | });
118 | }}>
119 | {String(isError)}
120 |
121 | )
122 | }
123 | }
124 |
125 | const SafeCenter = withErrorBoundary({
126 | renderBoundary: ({error}) => {
127 | return catch error: {error.message};
128 | },
129 | })(BugCenter);
130 | ```
131 |
132 | ## LICENSE
133 |
134 | MIT
135 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-error-helper",
3 | "version": "0.2.2",
4 | "description": "A helper for React Native to catch global JS errors and provide some ways to resolve error boundaries",
5 | "main": "dist/index.js",
6 | "typings": "dist/index.d.ts",
7 | "files": [
8 | "dist"
9 | ],
10 | "scripts": {
11 | "build": "yarn clean && yarn tsc",
12 | "clean": "rm -rf dist/*",
13 | "lint": "eslint src --ext .ts,.tsx"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/iChengbo/react-native-error-helper.git"
18 | },
19 | "keywords": [
20 | "react",
21 | "native",
22 | "error",
23 | "helper",
24 | "boundary",
25 | "hoc"
26 | ],
27 | "peerDependencies": {
28 | "react": ">= 16.8",
29 | "react-native": "*"
30 | },
31 | "author": "iChengbo",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/iChengbo/react-native-error-helper/issues"
35 | },
36 | "homepage": "https://github.com/iChengbo/react-native-error-helper#readme",
37 | "devDependencies": {
38 | "@react-native-community/eslint-config": "^3.0.1",
39 | "@types/react": "^18.0.21",
40 | "@types/react-native": "^0.64.4",
41 | "@typescript-eslint/eslint-plugin": "^5.5.0",
42 | "@typescript-eslint/parser": "^5.5.0",
43 | "eslint": "^8.3.0",
44 | "react": "^18.2.0",
45 | "react-native": "^0.64.0",
46 | "typescript": "^4.2.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/errorBoundary/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | ComponentLifecycle
4 | } from 'react';
5 | import { Text } from 'react-native';
6 |
7 | type ComponentDidCatch = ComponentLifecycle<{}, {}>["componentDidCatch"];
8 |
9 | export interface ErrorBoundaryProps {
10 | /**
11 | * Render Component when the ErrorBoundary catches an error.
12 | * You can custom ErrorBoundary component.
13 | */
14 | renderBoundary?: (error: any) => React.ReactNode | JSX.Element
15 | /**
16 | * Gets called when the ErrorBoundary catches an error.
17 | */
18 | onDidCatch?: NonNullable
19 | children?: React.ReactNode | JSX.Element
20 | }
21 |
22 | /**
23 | * Internal ErrorBoundary state.
24 | */
25 | export interface ErrorBoundaryState {
26 | hasError: boolean
27 | error: any
28 | }
29 |
30 | /**
31 | * ErrorBoundary class
32 | * Catches errors using lifecycle methods and renders fallback ui using children or renderBoundary props.
33 | */
34 | export class ErrorBoundary extends Component {
35 | constructor(props: ErrorBoundaryProps) {
36 | super(props);
37 | this.state = {
38 | hasError: false,
39 | error: null,
40 | };
41 | }
42 |
43 | static getDerivedStateFromError(error: any) {
44 | return { hasError: true, error };
45 | }
46 |
47 | componentDidCatch(...args: Parameters>) {
48 | const { onDidCatch } = this.props;
49 | onDidCatch && onDidCatch(...args);
50 | }
51 |
52 | render() {
53 | const { renderBoundary, children } = this.props;
54 | const { hasError, error } = this.state;
55 | if (hasError) {
56 | if (renderBoundary && typeof renderBoundary === 'function') {
57 | return renderBoundary({ error });
58 | } else {
59 | return Something went wrong.
60 | }
61 | }
62 | return children;
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/src/errorBoundary/withErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentType } from 'react';
2 | import { ErrorBoundary, ErrorBoundaryProps } from './ErrorBoundary';
3 |
4 | /**
5 | * React HOC for creating ErrorBoundaryWrapper component.
6 | * @param params.renderBoundary
7 | * @param params.onDidCatch
8 | * @returns
9 | */
10 | export const withErrorBoundary = (params: ErrorBoundaryProps) => (WrappedComponent: ComponentType) => {
11 | const { renderBoundary, onDidCatch } = params;
12 | return (props: any) => {
13 | return (
14 |
15 |
16 |
17 | )
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/globalError/index.ts:
--------------------------------------------------------------------------------
1 | export type JSErrorHandler = (error: Error, isFatal: boolean) => void;
2 |
3 | const noop: JSErrorHandler = () => { };
4 |
5 | /**
6 | * set a handler to catch global error
7 | * @param {Function} customHandler
8 | * @param {Boolean} allowedInDevMode
9 | * @returns
10 | */
11 | export const setGlobalErrorHandler = (customHandler: JSErrorHandler = noop, allowedInDevMode = false) => {
12 | if (typeof allowedInDevMode !== 'boolean' || typeof customHandler !== 'function') {
13 | console.log('setGlobalErrorHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean');
14 | console.log('Not setting the JS handler .. please fix setGlobalErrorHandler call');
15 | return;
16 | }
17 | // @ts-ignore
18 | const allowed = allowedInDevMode ? true : !__DEV__;
19 | if (allowed) {
20 | // @ts-ignore
21 | global.ErrorUtils.setGlobalHandler(customHandler);
22 | const consoleError = console.error;
23 | // @ts-ignore
24 | console.error = (...args) => {
25 | // @ts-ignore
26 | global.ErrorUtils.reportError(...args);
27 | consoleError(...args);
28 | };
29 | } else {
30 | console.log(
31 | 'Skipping setGlobalErrorHandler: Reason: In DEV mode and allowedInDevMode = false',
32 | );
33 | }
34 | };
35 | /**
36 | *
37 | * @returns
38 | */
39 | // @ts-ignore
40 | export const getGlobalErrorHandler: () => JSErrorHandler = () => global.ErrorUtils.getGlobalHandler();
41 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getGlobalErrorHandler,
3 | setGlobalErrorHandler,
4 | } from './globalError';
5 |
6 | import { setPromiseUnCatchHandler } from './promiseTracker';
7 |
8 | import { ErrorBoundary } from './errorBoundary/ErrorBoundary';
9 | import { withErrorBoundary } from './errorBoundary/withErrorBoundary';
10 |
11 | export {
12 | setGlobalErrorHandler,
13 | getGlobalErrorHandler,
14 | setPromiseUnCatchHandler,
15 | ErrorBoundary,
16 | withErrorBoundary,
17 | };
18 |
--------------------------------------------------------------------------------
/src/promiseTracker/index.ts:
--------------------------------------------------------------------------------
1 | export type PromiseUnCatchHandler = (id: string, error: Error) => void;
2 |
3 | const noop: PromiseUnCatchHandler = () => { };
4 |
5 | /**
6 | *
7 | * @param {Function} customHandler
8 | * @param {Boolean} allowedInDevMode
9 | * @returns
10 | */
11 | export const setPromiseUnCatchHandler = (customHandler: PromiseUnCatchHandler = noop, allowedInDevMode = false) => {
12 | if (typeof customHandler !== 'function' || typeof allowedInDevMode !== 'boolean') {
13 | console.log('setPromiseUnCatchHandler is called with wrong argument types.. first argument should be callback function and second argument is optional should be a boolean');
14 | console.log('Not setting the JS handler .. please fix setPromiseUnCatchHandler call');
15 | return;
16 | }
17 | // @ts-ignore
18 | const allowed = allowedInDevMode ? true : !__DEV__;
19 | if (allowed) {
20 | // @ts-ignore
21 | require('promise/setimmediate/rejection-tracking').enable({
22 | allRejections: true,
23 | onUnhandled: customHandler,
24 | onHandled: (id: string) => {
25 | const warning =
26 | `Promise Rejection Handled (id: ${id})\n` +
27 | 'This means you can ignore any previous messages of the form ' +
28 | `"Possible Unhandled Promise Rejection (id: ${id}):"`;
29 | console.warn(warning);
30 | },
31 | });
32 | } else {
33 | console.log('Skipping setPromiseUnCatchHandler: Reason: In DEV mode and allowedInDevMode = false');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "jsx": "react",
5 | "forceConsistentCasingInFileNames": true,
6 | "isolatedModules": true,
7 | "module": "CommonJS",
8 | "moduleResolution": "node",
9 | "noEmitOnError": false,
10 | "outDir": "dist",
11 | "removeComments": false,
12 | "resolveJsonModule": true,
13 | "rootDir": "src",
14 | "skipLibCheck": true,
15 | "sourceMap": false,
16 | "strict": true,
17 | "target": "ES2015",
18 | "declaration": true
19 | },
20 | "exclude": ["node_modules", "dist", "examples"]
21 | }
--------------------------------------------------------------------------------