├── .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](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) 7 | [![npm-version](https://img.shields.io/npm/v/react-native-error-helper)](https://www.npmjs.com/package/react-native-error-helper) 8 | [![npm](https://img.shields.io/npm/dm/react-native-error-helper.svg)](https://www.npmjs.com/package/react-native-error-helper) 9 | [![](https://img.shields.io/github/release-date/iChengbo/react-native-error-helper)](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 | } --------------------------------------------------------------------------------