How do I set up custom error handling with React?

Since version 16, React has shipped with a nice error handling mechanism for components, called error boundaries. These allow you to catch errors from child components and prevent them from propagating up the tree. Conceptually, this works like try-catch, but for the component tree instead of the call stack.

While this is a nice mechanism for components, if you want to set up a global, all-catching error handler, this mechanism is not enough. We will describe error boundaries below, and see what needs to be done in order to actually catch all errors that can be generated in your app.

Have you ever tried sleeping with a broken code base?

Error Boundaries

You define an error boundary by defining the componentDidCatch(error, info) method on your component. When this is defined, the error will not propagate up to the parent component. The method is called in the "commit" phase, so side effects are allowed here. If you want to define logic for error logging, this method is a good place to put it.

class MyErrorBoundary extends React.Component {   
    [...]
    componentDidCatch(error, info) {
        console.log(error);
    }
}

When a component has an error, it will not render at all. This behaviour is new in React 16. The decision was made that showing no UI was better than showing a possibly broken UI. Using error boundaries allow you to bypass this all-or-nothing behaviour. Since error boundaries stop the propagation of errors, they allow parent components to continue to render, instead of taking down the entire UI.

As of React 16.6, you can also define the static getDerivedStateFromError() method, in order to compute a new state based on the caught exception. The method should return a value with which the state should be updated. Since this is called during the "render" phase, side effects are not allowed in this method, but it is a good place to put logic to compute the next state, e.g. to show an error message to the user.

class MyErrorBoundary extends React.Component {   
    [...]
    componentDidCatch(error, info) {
        console.log(error);
    }

    static getDerivedStateFromError(error) {
        return { failure: true, exception: error };
    }

    render() {
        if (this.state.failure) {
            return <h1>I listened to your problems, now listen to mine: {this.state.exception}</h1>;
        }
        return this.props.children; 
    }
}

This will not catch all errors!

Error boundaries only apply to errors that happen during rendering. So errors originating anywhere else will not trigger this mechanism. This includes errors in event handlers, and errors in async calls (e.g. setTimeout(...) and similar).

If you want to apply a global error handler in your React app, you either have to wrap everything in a try-catch, or you can just resort to some good old 90s-era browser technology, and assign an error handler to the window object:

window.onerror = function(msg, src, lineno, colno, error) { /*your code*/ }

Be aware that what arguments are actually passed here varies by browser.

How do I log all errors globally?

If you want to log the errors that occur client side, and persist the errors to a server, you can do this by simply dropping in the catch.js script.

<script src="//cdn.catchjs.com/catch.js"></script>

This will automatically set up a global error handler in the browser, and catch all errors that reach it. These will then be logged to your CatchJS account, along with the telemetry needed to reproduce them. This part is framework agnostic, so it needs no set up from you.

If you also want to catch errors in your components, and log them to your CatchJS log, you can do so by defining componentDidCatch(...) and passing the error object onto console.error(...), as shown in the example above. CatchJS automatically instruments the console.error method, such that when an error object is passed to it, it will be persisted to the CatchJS error log, along with relevant telemetry.