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/