├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example ├── index.html └── src │ ├── FilterableProductTable.jsx │ └── index.jsx ├── package-lock.json ├── package.json ├── src ├── Fallback.js ├── Modal.js ├── WithErrorHandler.js └── index.js ├── webpack.config.js └── webpack.dev.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": { 4 | "presets": [ 5 | "es2015", 6 | "react", 7 | "stage-3" 8 | ], 9 | "plugins": [ 10 | "transform-decorators-legacy" 11 | ] 12 | }, 13 | "es": { 14 | "presets": [ 15 | "es2015-rollup", 16 | "react", 17 | "stage-3" 18 | ], 19 | "plugins": [ 20 | "transform-runtime", 21 | "transform-decorators-legacy" 22 | ] 23 | }, 24 | "production": { 25 | "comments":false, 26 | "presets": [ 27 | "es2015", 28 | "react", 29 | "stage-3" 30 | ], 31 | "plugins": [ 32 | "transform-runtime", 33 | "transform-decorators-legacy" 34 | ] 35 | } 36 | }, 37 | "presets": [ 38 | "es2015", 39 | "react", 40 | "stage-3" 41 | ], 42 | "plugins": [ 43 | "transform-decorators-legacy" 44 | ] 45 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | build 60 | dist 61 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | .DS_STORE 40 | *.log 41 | example 42 | examples 43 | build 44 | src 45 | LICENSE 46 | webpack.*.js 47 | *.js.map -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chen HaiYong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-error-boundaries 2 | 3 | [![version](https://img.shields.io/npm/v/react-error-boundaries.svg?style=flat-square)](http://npm.im/react-popconfirm) 4 | [![downloads](https://img.shields.io/npm/dm/react-error-boundaries.svg?style=flat-square)](http://npm-stat.com/charts.html?package=react-popconfirm&from=2017-04-07) 5 | [![MIT License](https://img.shields.io/npm/l/react-error-boundaries.svg?style=flat-square)](http://opensource.org/licenses/MIT) 6 | 7 | A reusable React error boundaries component. Based on React 16.2.0. 8 | 9 | > Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. 10 | 11 | Effect picture: 12 | 13 | ![react-error-boundaries](https://raw.githubusercontent.com/Chyrain/MDPictures/master/res/react_error_boundaries.png) 14 | 15 | ## Install 16 | 17 | ```s 18 | npm install react-error-boundaries 19 | ``` 20 | 21 | ## Usage 22 | 23 | Import: 24 | 25 | ```js 26 | // import all 27 | import { ErrorBoundary, withErrorHandler, errorHandlerDecorator, FallbackView } from 'react-error-boundaries' 28 | // import default ErrorBoundary 29 | import ErrorBoundary from 'react-error-boundaries' 30 | ``` 31 | 32 | Intro: 33 | 34 | - **ErrorBoundary**: React container component to handler error 35 | - **withErrorHandler**: React HOC to customize the errorCallback function and FallbackComponent 36 | - **errorHandlerDecorator**: By this, you can use error boundary as ES7 decorator 37 | - **FallbackView**: The default fallback component, show when error occur. props: `{ error: Objec, errorInfo: Object, closeErrorModal: Function }`(Unable in production, if import you will got undefined) 38 | 39 | Use as a component container: 40 | 41 | ```js 42 | // import first 43 | import React from 'react' 44 | import ReactDOM from 'react-dom' 45 | import ErrorBoundary from 'react-error-boundaries' 46 | 47 | const App = () => { 48 | return ( 49 | 50 | 51 | 52 | ); 53 | } 54 | ReactDOM.render(, document.getElementById('root')); 55 | ``` 56 | 57 | And you can handle errors by providing an onError callback, and customize the FallbackComponent by providing a Component. 58 | 59 | > FallbackComponent will receive props: 60 | - closeErrorModal: Function, call when click close button 61 | - error: An error that has been thrown. 62 | - errorInfo: An object with componentStack key. The property has information about component stack during thrown error. 63 | 64 | ```js 65 | // import first 66 | import React from 'react' 67 | import ReactDOM from 'react-dom' 68 | import ErrorBoundary from 'react-error-boundaries' 69 | 70 | function onError(error, errorInfo, props) { 71 | // you can report Error to service here 72 | console.error('onError:', error.message); 73 | } 74 | 75 | const App = () => { 76 | return ( 77 | 78 | 79 | 80 | ); 81 | } 82 | ReactDOM.render(, document.getElementById('root')); 83 | ``` 84 | 85 | Use as class decorator: 86 | 87 | ```js 88 | // import first 89 | import React from 'react' 90 | import { errorHandlerDecorator } from 'react-error-boundaries' 91 | 92 | // ES7 decorator, need babel plugin "transform-decorators-legacy" 93 | @errorHandlerDecorator 94 | class YourComponent extends React.Component { 95 | constructor(props) { 96 | super(props); 97 | } 98 | 99 | render() { 100 | return ( 101 |
102 | contents 103 |
104 | ); 105 | } 106 | } 107 | // or not use @decorator just like this: 108 | // export default errorHandlerDecorator(YourComponent) 109 | 110 | function onError(error, errorInfo, props) { 111 | // you can report Error to service here 112 | console.log('onError:', error.message); 113 | } 114 | 115 | ReactDOM.render(, document.getElementById('root')); 116 | ``` 117 | 118 | You can also customize the FallbackComponent in HOC way: 119 | 120 | ```js 121 | // import first, FallbackView is default Fallback Component 122 | import { withErrorHandler, FallbackView } from 'react-error-boundaries' 123 | 124 | // customize the errorCallback 125 | function onError(error, errorInfo, props) { 126 | // you can report Error to service here 127 | console.error('onError:', error.message); 128 | } 129 | 130 | /* example 1 */ 131 | 132 | const ComponentWithErrorBoundary = withErrorHandler( 133 | YourFallbackComponent, // Fallback Component to display errors, to replace default FallbackView 134 | YourComponent // Component to decorate 135 | ); 136 | ReactDOM.render(, document.getElementById('root')); 137 | 138 | /* example 2 */ 139 | // customize as a ES7 decorator 140 | const yourErrorHandlerDecorator = withErrorHandler( 141 | YourFallbackComponent // Fallback Component to display errors, to replace default FallbackView 142 | ); 143 | 144 | @yourErrorHandlerDecorator 145 | class YourComponent extends React.component { 146 | //...... 147 | } 148 | ReactDOM.render(, document.getElementById('root')); 149 | ``` 150 | 151 | ## Try example 152 | 153 | Input `i` in search input and error will throw. 154 | 155 | ```shell 156 | # run example, auto open browser and enable hot loader 157 | npm install 158 | npm start 159 | ``` 160 | 161 | ## How to disable it 162 | 163 | To enable it by set `process.env.NODE_ENV` or `process.env.ERROR_ENV` as `development`, so you can disable it by setting `process.env.NODE_ENV` to be `production` and not set `process.env.ERROR_ENV` as `development`. 164 | 165 | With webpack by setting like this to disable it: 166 | 167 | ```js 168 | plugins: [ 169 | new webpack.DefinePlugin({ 170 | "process.env": { 171 | NODE_ENV: '"production"' 172 | } 173 | }) 174 | ] 175 | ``` 176 | 177 | With config like this to enable it even in NODE_ENV is production: 178 | 179 | ```js 180 | plugins: [ 181 | new webpack.DefinePlugin({ 182 | "process.env": { 183 | NODE_ENV: '"production"', 184 | ERROR_ENV: '"development"' 185 | } 186 | }) 187 | ] 188 | ``` 189 | 190 | ## License 191 | 192 | MIT -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Error Boundary 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /example/src/FilterableProductTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { errorHandlerDecorator } from '../../dist/commonjs' 3 | 4 | class ProductCategoryRow extends React.PureComponent { 5 | render() { 6 | return ({this.props.category}); 7 | } 8 | } 9 | 10 | class ProductRow extends React.PureComponent { 11 | render() { 12 | var name = this.props.product.stocked ? 13 | this.props.product.name : 14 | 15 | {this.props.product.name} 16 | ; 17 | return ( 18 | 19 | {name} 20 | {this.props.product.price} 21 | 22 | ); 23 | } 24 | } 25 | 26 | class ProductTable extends React.PureComponent { 27 | render() { 28 | if (this.props.filterText === 'i') { 29 | // Simulate a JS error 30 | throw new Error('I crashed! (Test Error Boundaries when input "i")'); 31 | } 32 | var rows = []; 33 | var lastCategory = null; 34 | console.log('filter:' + this.props.filterText, 'inStockOnly:' + this.props.inStockOnly) 35 | this.props.products.forEach((product) => { 36 | if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) { 37 | return; 38 | } 39 | if (product.category !== lastCategory) { 40 | rows.push(); 41 | } 42 | rows.push(); 43 | lastCategory = product.category; 44 | }); 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {rows} 54 |
NamePrice
55 | ); 56 | } 57 | } 58 | 59 | class SearchBar extends React.PureComponent { 60 | constructor(props) { 61 | super(props); 62 | this.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this); 63 | this.handleInStockInputChange = this.handleInStockInputChange.bind(this); 64 | } 65 | 66 | handleFilterTextInputChange(e) { 67 | this.props.onFilterTextInput(e.target.value); 68 | } 69 | 70 | handleInStockInputChange(e) { 71 | this.props.onInStockInput(e.target.checked); 72 | } 73 | 74 | render() { 75 | return ( 76 |
77 | 83 |

84 | 89 | {' '} 90 | Only show products in stock 91 |

92 |
93 | ); 94 | } 95 | } 96 | 97 | function onError(error, errorInfo, props) { 98 | console.log('FilterableProductTable.onError:', error, errorInfo, props); 99 | } 100 | 101 | // @errorHandlerDecorator // use ES7 decorator 102 | export default class FilterableProductTable extends React.PureComponent { 103 | constructor(props) { 104 | super(props); 105 | this.state = { 106 | filterText: '', 107 | inStockOnly: false 108 | }; 109 | 110 | this.handleFilterTextInput = this.handleFilterTextInput.bind(this); 111 | this.handleInStockInput = this.handleInStockInput.bind(this); 112 | } 113 | 114 | handleFilterTextInput(filterText) { 115 | this.setState({ 116 | filterText: filterText 117 | }); 118 | } 119 | 120 | handleInStockInput(inStockOnly) { 121 | this.setState({ 122 | inStockOnly: inStockOnly 123 | }) 124 | } 125 | 126 | render() { 127 | return ( 128 |
129 | 135 | 140 |
141 | ); 142 | } 143 | } 144 | 145 | 146 | var PRODUCTS = [ 147 | { category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football' }, 148 | { category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball' }, 149 | { category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball' }, 150 | { category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch' }, 151 | { category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5' }, 152 | { category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7' } 153 | ]; 154 | 155 | export { PRODUCTS }; 156 | // or not use @decorator 157 | // export default errorHandlerDecorator(FilterableProductTable) -------------------------------------------------------------------------------- /example/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import FilterableProductTable, { PRODUCTS } from './FilterableProductTable' 5 | import ErrorBoundary from '../../dist/commonjs' 6 | 7 | const App = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | function onError(error, errorInfo, props) { 16 | console.warn('App.onError:', error, errorInfo, props); 17 | } 18 | 19 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-error-boundaries", 3 | "version": "1.1.4", 4 | "description": "React HOC for error boundaries.", 5 | "main": "dist/commonjs/index.js", 6 | "scripts": { 7 | "build": "npm run build:commonjs && npm run build:es && npm run build:umd", 8 | "build:commonjs": "npm run clean:commonjs && cross-env NODE_ENV=production cross-env BABEL_ENV=commonjs babel src --out-dir dist/commonjs", 9 | "build:es": "npm run clean:es && cross-env NODE_ENV=production cross-env BABEL_ENV=es babel src --out-dir dist/es", 10 | "build:umd": "npm run clean:umd && cross-env NODE_ENV=production npm run webpack:umd", 11 | "clean": "npm run clean:commonjs && npm run clean:es && npm run clean:umd", 12 | "clean:commonjs": "rimraf dist/commonjs", 13 | "clean:es": "rimraf dist/es", 14 | "clean:umd": "rimraf dist/umd", 15 | "webpack": "webpack --progress --colors --bail --define", 16 | "webpack:umd": "npm run webpack umd", 17 | "build:example": "webpack --config webpack.dev.config.js --progress --colors", 18 | "start": "webpack-dev-server --config webpack.dev.config.js --progress --colors --hot --inline" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/Chyrain/react-error-boundaries.git" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "HOC", 27 | "error", 28 | "boundary" 29 | ], 30 | "author": "Chyrain. (https://chyrain.github.io/)", 31 | "bugs": { 32 | "url": "https://github.com/Chyrain/react-error-boundaries/issues" 33 | }, 34 | "homepage": "https://github.com/Chyrain/react-error-boundaries#readme", 35 | "license": "MIT", 36 | "peerDevDependencies": { 37 | "react": "^16.0.0", 38 | "react-dom": "^16.0.0" 39 | }, 40 | "devDependencies": { 41 | "babel-core": "^6.26.0", 42 | "babel-loader": "^7.1.2", 43 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 44 | "babel-plugin-transform-runtime": "^6.23.0", 45 | "babel-preset-es2015": "^6.24.1", 46 | "babel-preset-es2015-rollup": "^3.0.0", 47 | "babel-preset-react": "^6.24.1", 48 | "babel-preset-stage-3": "^6.24.1", 49 | "cross-env": "^5.1.3", 50 | "html-webpack-plugin": "^2.30.1", 51 | "open-browser-webpack-plugin": "0.0.5", 52 | "webpack": "^3.10.0", 53 | "webpack-dev-server": "^2.9.7", 54 | "react": "^16.2.0", 55 | "react-dom": "^16.2.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Fallback.js: -------------------------------------------------------------------------------- 1 | // Fallback.jsx 2 | import React from 'react' 3 | import Modal from './Modal' 4 | 5 | const containerStyl = { 6 | boxSizing: 'border-box', 7 | backgroundColor: 'rgba(0,0,0,0.75)', 8 | color: '#fff', 9 | position: 'fixed', 10 | height: '100%', 11 | width: '100%', 12 | top: 0, 13 | left: 0, 14 | overflowY: 'auto', 15 | padding: '16px' 16 | }; 17 | const btnStyl = { 18 | position: 'absolute', 19 | right: '16px', 20 | top: '35px', 21 | cursor: 'pointer', 22 | minWidth: '40px', 23 | minHeight: '30px', 24 | borderRadius: '5px', 25 | outlineStyle: 'none' 26 | }; 27 | const preSty = { 28 | whiteSpace: 'pre-wrap', 29 | wordWrap: 'break-word', 30 | wordBreak: 'break-word', 31 | padding: '0 6px' 32 | }; 33 | const detailSty = { 34 | outline: 'none', 35 | WebkitTapHighlightColor: 'transparent', 36 | WebkitHighlight: 'none' 37 | }; 38 | 39 | export default function Fallback(props) { 40 | const { error, errorInfo, closeErrorModal } = props; 41 | 42 | return ( 43 | 44 |
45 | 46 |
47 |

Something went wrong.

48 |
49 |

50 | ErrorMessage 51 |

52 |
{error.message}
53 |
54 |

55 | Stack 56 |

57 |
{error.stack}
58 |
59 |

60 | ComponentStack 61 |

62 |
{errorInfo.componentStack}
63 |
64 |
65 |
66 |
67 | ) 68 | } -------------------------------------------------------------------------------- /src/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | export default class Modal extends React.PureComponent { 5 | constructor(props) { 6 | super(props); 7 | // Create a div that we'll render the modal into. Because each 8 | // Modal component has its own element, we can render multiple 9 | // modal components into the modal container. 10 | this.el = document.createElement('div'); 11 | } 12 | 13 | componentDidMount() { 14 | // Append the element into the DOM on mount. We'll render 15 | // into the modal container element (see the HTML tab). 16 | document.body.appendChild(this.el); 17 | } 18 | 19 | componentWillUnmount() { 20 | // Remove the element from the DOM when we unmount 21 | document.body.removeChild(this.el); 22 | } 23 | 24 | render() { 25 | // Use a portal to render the children into the element 26 | return ReactDOM.createPortal( 27 | // Any valid React child: JSX, strings, arrays, etc. 28 | this.props.children, 29 | // A DOM element 30 | this.el 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /src/WithErrorHandler.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import FallbackView from "./Fallback"; 3 | 4 | class ErrorBoundary extends React.PureComponent { 5 | constructor() { 6 | super(); 7 | 8 | this.closeErrorModal = this.closeErrorModal.bind(this); 9 | this.state = { 10 | hasError: false, 11 | error: null, 12 | errorInfo: null 13 | }; 14 | } 15 | 16 | closeErrorModal() { 17 | this.setState({ hasError: false }); 18 | } 19 | 20 | componentDidCatch(error, info) { 21 | // Update state if error happens 22 | this.setState({ hasError: true, error, errorInfo: info }); 23 | 24 | // Report errors here 25 | const { onError, FallbackComponent, ..._props } = this.props; 26 | if (typeof onError === "function") { 27 | try { 28 | onError.call(this, error, info, _props); 29 | } catch (e) {} 30 | } 31 | } 32 | 33 | render() { 34 | const { onError, FallbackComponent, children, ..._props } = this.props; 35 | // if state contains error and in development environment we render fallback component 36 | if (this.state.hasError) { 37 | const { error, errorInfo } = this.state; 38 | return ( 39 | 45 | ); 46 | } 47 | return children; 48 | } 49 | } 50 | ErrorBoundary.defaultProps = { 51 | FallbackComponent: FallbackView 52 | }; 53 | 54 | export { ErrorBoundary, FallbackView }; 55 | export default ErrorBoundary; 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | let __ErrorBoundary; 4 | if ( 5 | process.env.NODE_ENV === "development" || 6 | process.env.ERROR_ENV === "development" 7 | ) { 8 | const { ErrorBoundary, FallbackView } = require("./WithErrorHandler"); 9 | 10 | const withErrorHandler = curry((FallbackComponent, Component) => { 11 | const WithErrorHandler = props => { 12 | const { onError } = props; 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | WithErrorHandler.displayName = `WithErrorHandler(${Component.displayName || 20 | Component.name || 21 | "Component"})`; 22 | return WithErrorHandler; 23 | }); 24 | __ErrorBoundary = ErrorBoundary; 25 | exports.ErrorBoundary = ErrorBoundary; 26 | exports.FallbackView = FallbackView; 27 | exports.withErrorHandler = withErrorHandler; 28 | exports.errorHandlerDecorator = withErrorHandler(FallbackView); 29 | } else { 30 | // production or other env (not development) 31 | // NOOP ErrorBoundary 32 | class ErrorBoundary extends React.Component { 33 | componentDidCatch(error, info) { 34 | const { onError, ..._props } = this.props; 35 | if (typeof onError === "function") { 36 | try { 37 | onError.call(this, error, info, _props); 38 | } catch (e) {} 39 | } 40 | } 41 | 42 | render() { 43 | return this.props.children; 44 | } 45 | } 46 | // NOOP HOC 47 | const withErrorHandler = curry((FallbackComponent, Component) => { 48 | const WithErrorHandler = props => { 49 | const { onError } = props; 50 | return ( 51 | 52 | 53 | 54 | ); 55 | }; 56 | return WithErrorHandler; 57 | }); 58 | __ErrorBoundary = ErrorBoundary; 59 | exports.ErrorBoundary = ErrorBoundary; 60 | exports.withErrorHandler = withErrorHandler; 61 | exports.errorHandlerDecorator = withErrorHandler(void 0); 62 | } 63 | 64 | function curry(fn) { 65 | if (typeof fn !== "function") { 66 | throw Error("curry only receive function params!"); 67 | } 68 | let _len = fn.length, 69 | _args = []; 70 | 71 | function _curry() { 72 | var args = [].concat(_args); 73 | if (arguments.length >= _len) { 74 | _args = []; 75 | } else if (arguments.length + _args.length > _len) { 76 | _args = []; 77 | } 78 | _args = _args.concat([].slice.call(arguments)); 79 | if (_args.length === _len) { 80 | var rst = fn.apply(null, _args); 81 | _args = args; 82 | return rst; 83 | } 84 | return _curry; 85 | } 86 | _curry.toString = function() { 87 | return fn.toString(); 88 | }; 89 | return _curry; 90 | } 91 | 92 | export default __ErrorBoundary; 93 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | const path = require('path'); 3 | const webpack = require('webpack'); 4 | const argv = require('yargs').argv; 5 | const HtmlwebpackPlugin = require('html-webpack-plugin'); 6 | 7 | const SRC_PATH = path.resolve(__dirname, 'src'); 8 | const BUILD_PATH = path.resolve(__dirname, 'dist'); 9 | const EXAMPLE_PATH = path.resolve(__dirname, 'example'); 10 | 11 | const libTarget = argv.define || 'commonjs'; 12 | 13 | module.exports = { 14 | // Source Maps("source-map|cheap-module-source-map|eval-source-map|cheap-module-eval-source-map") 15 | devtool: 'source-map', 16 | entry: { 17 | index: path.resolve(SRC_PATH, 'index.js') 18 | }, 19 | output: { 20 | path: path.resolve(BUILD_PATH, libTarget), 21 | filename: '[name].js', 22 | libraryTarget: libTarget, 23 | library: 'ReactErrorBoundaries' 24 | }, 25 | resolve: { 26 | extensions: ['.js', '.jsx'] 27 | }, 28 | externals: { 29 | 'react': 'React', 30 | 'react-dom': 'ReactDOM' 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.js[x]?$/, 36 | include: [ 37 | SRC_PATH 38 | ], 39 | loader: 'babel-loader' 40 | } 41 | ] 42 | }, 43 | plugins: [ 44 | new webpack.optimize.UglifyJsPlugin({ 45 | compress: { 46 | warnings: false 47 | }, 48 | beautify: true, 49 | comments: true, 50 | mangle: false 51 | }), 52 | new webpack.BannerPlugin('Copyright © 2017 by Chyrain. All rights reserved.') 53 | ] 54 | } -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | const path = require("path"); 3 | const webpack = require("webpack"); 4 | const HtmlwebpackPlugin = require("html-webpack-plugin"); 5 | const OpenBrowserPlugin = require("open-browser-webpack-plugin"); 6 | 7 | const SRC_PATH = path.resolve(__dirname, "src"); 8 | const BUILD_PATH = path.resolve(__dirname, "build"); 9 | const EXAMPLE_PATH = path.resolve(__dirname, "example"); 10 | const EXAMPLE_SRC_PATH = path.resolve(__dirname, "example/src"); 11 | 12 | module.exports = { 13 | // Source Maps("source-map|cheap-module-source-map|eval-source-map|cheap-module-eval-source-map") 14 | devtool: "cheap-module-source-map", 15 | entry: { 16 | // webpack: [ 17 | // "webpack-dev-server/client?http://0.0.0.0:8080", 18 | // "webpack/hot/only-dev-server" 19 | // ], 20 | index: path.resolve(EXAMPLE_SRC_PATH, "index.jsx") 21 | }, 22 | output: { 23 | path: BUILD_PATH, 24 | filename: "[name].js" 25 | }, 26 | resolve: { 27 | extensions: [".js", ".jsx"] 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.js[x]?$/, 33 | include: [SRC_PATH, EXAMPLE_SRC_PATH], 34 | loader: "babel-loader" 35 | } 36 | ] 37 | }, 38 | plugins: [ 39 | new webpack.DefinePlugin({ 40 | "process.env": { 41 | NODE_ENV: '"production"', 42 | ERROR_ENV: '"development"' 43 | } 44 | }), 45 | // new webpack.optimize.UglifyJsPlugin({ 46 | // compress: { 47 | // warnings: false 48 | // }, 49 | // sourceMap: true 50 | // }), 51 | new OpenBrowserPlugin({ url: "http://localhost:8080" }), 52 | new webpack.BannerPlugin( 53 | "Copyright © 2017 by Chyrain. All rights reserved." 54 | ), 55 | new HtmlwebpackPlugin({ 56 | template: path.resolve(EXAMPLE_PATH, "./index.html"), 57 | filename: "index.html", 58 | inject: "body" 59 | }) 60 | ] 61 | }; 62 | --------------------------------------------------------------------------------