can you catch all errors of a React.js app with a try/catch block?
I've made a react application which is not running live, and the people that use it note that very occasionally some strange error occurs. I don't know why or what happens, and can't reproduce it.
So I'm wondering if there is a way to wrap the entire app, or parts of it, in a try/catch block so that I can send the errors to an error log on the server?
All I've read so far is that you could wrap the entire render function in a try/catch, but that would not catch any errors due to user interation right?
React 16 introduced Error Boundaries and the componentDidCatch lifecycle method:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Then you can use it as a regular component:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Or you can wrap your root component with the npm package react-error-boundary, and set a fallback component and behavior.
import {ErrorBoundary} from 'react-error-boundary';
const myErrorHandler = (error: Error, componentStack: string) => {
// ...
};
<ErrorBoundary onError={myErrorHandler}>
<ComponentThatMayError />
</ErrorBoundary>
this is what I ended up using
EDIT: React 16 introduced proper ways to do this, see @goldylucks answer.
componentWillMount() {
this.startErrorLog();
}
startErrorLog() {
window.onerror = (message, file, line, column, errorObject) => {
column = column || (window.event && window.event.errorCharacter);
var stack = errorObject ? errorObject.stack : null;
//trying to get stack from IE
if (!stack) {
var stack = [];
var f = arguments.callee.caller;
while (f) {
stack.push(f.name);
f = f.caller;
}
errorObject['stack'] = stack;
}
var data = {
message: message,
file: file,
line: line,
column: column,
errorStack: stack
};
//here I make a call to the server to log the error
//the error can still be triggered as usual, we just wanted to know what's happening on the client side
return false;
};
}
You can leverage React's BatchingStrategy API to easily wrap a try/catch
around all of your React code. The benefit of this over window.onerror
is that you get a nice stack trace in all browsers. Even modern browsers like Microsoft Edge and Safari don't provide stack traces to window.onerror
.
Here's what it looks like with React 15.4:
import ReactUpdates from "react-dom/lib/ReactUpdates";
import ReactDefaultBatchingStrategy from "react-dom/lib/ReactDefaultBatchingStrategy";
let isHandlingError = false;
const ReactTryCatchBatchingStrategy = {
// this is part of the BatchingStrategy API. simply pass along
// what the default batching strategy would do.
get isBatchingUpdates () { return ReactDefaultBatchingStrategy.isBatchingUpdates; },
batchedUpdates (...args) {
try {
ReactDefaultBatchingStrategy.batchedUpdates(...args);
} catch (e) {
if (isHandlingError) {
// our error handling code threw an error. just throw now
throw e;
}
isHandlingError = true;
try {
// dispatch redux action notifying the app that an error occurred.
// replace this with whatever error handling logic you like.
store.dispatch(appTriggeredError(e));
} finally {
isHandlingError = false;
}
}
},
};
ReactUpdates.injection.injectBatchingStrategy(ReactTryCatchBatchingStrategy);
Full writeup here: https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/