can you catch all errors of a React.js app with a try/catch block?

Try CatchReactjs

Try Catch Problem Overview


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?

Try Catch Solutions


Solution 1 - Try Catch

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>

Solution 2 - Try Catch

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;
    };
  }

Solution 3 - Try Catch

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/

Solution 4 - Try Catch

Error boundaries are too limited and don't catch all errors.

In React 17, to catch all errors, like:

  • events from promises (event handlers on click),
  • as well as sync exceptions like undefined exception, etc

You need two global handlers:

// TypeScript

export function registerHandlers(store: Store) {
  
  window.addEventListener("error", (event) => {
    store.dispatch<any>(setErrorAction({ message: event.message }));
  });

  window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
    store.dispatch<any>(setErrorAction({ message: event.reason.message }));
  });
}

Invoke this after the Redux Store is created and as a result, all exceptions will be passed to Redux, so you can useSelector to get it and display or log somewhere (e.g. send to server for storage).

For better coverage on HTTP errors you can catch them on Axios Response Interceptor and push to store from there (you will get more information about the error). Just fiter it out on unhandledrejection (unhandled promise exception) or swallow in the interceptor, so it's not doubled.

Solution 5 - Try Catch

I had the same problem. I created an Office App where I neither had a debug console nor developer tools, so I couldn't found out where errors occured.

I created a single component (an es6-class) that catched all console messages, saved the message into a separate array and called the "real" console function.

log(message) {
    const msg = new Log(message);
    this.pushMessage(msg);
    this._target.log(message);
}

where Log is a simple wrapper with a message and a type and this._target is a reference on window.console. So I did the same with info, warn and error.

Additionally, I created a method handleThrownErrors(message, url, lineNumber) to catch exceptions.

window.onerror = this.handleThrownErrors.bind(this);

At least I created an instance of the class (i called it LogCollector) and appended it to the window.

window.logCollector = new LogCollector();

Now I created an react component that gets the logCollector instance (window.logCollector) as property. In regular intervals the react component checks the collected messages and display them on the screen.

componentDidMount() {
    this.setInterval(this._forceUpdate, 500);
},

_forceUpdate() {
    this.setState({update: !this.state.update});
}

this.setInterval() is an own function that simply calls window.setInterval().

And in render() method:

return (
    <div class="console">
        {this.props.logCollector.getMessages().map(this.createConsoleMessage)}
    </div>
);

>NOTE: It is important to include the LogCollector before all other files.

>NOTE: The above solution as a very simplified version. For example: You can improve it by adding custom (message-) listeners, or catching 404 Not found errors (for js-scripts and css-files).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFlionView Question on Stackoverflow
Solution 1 - Try CatchgoldylucksView Answer on Stackoverflow
Solution 2 - Try CatchFlionView Answer on Stackoverflow
Solution 3 - Try CatchByron WongView Answer on Stackoverflow
Solution 4 - Try Catchandrew.foxView Answer on Stackoverflow
Solution 5 - Try CatchmarcelView Answer on Stackoverflow