Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript / React window.onerror fired twice

I want to globally catch errors in my React application.

But every time the error is caught/forwarded twice to my registered function.

Example code:

window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(msg)
  alert(msg)
}

class TodoApp extends React.Component {
  constructor(props) {
    super(props)


  }

  render() {

    return (
      <button onClick={(e)=>{
        console.log("clicked")
        null.bla
      }}>
        Create an error

      </button>
    )
  }
}

ReactDOM.render(<TodoApp />, document.querySelector("#app"))

Here is a JS-fiddle: https://jsfiddle.net/dmxur0rc/4/

The console only shows one 'clicked' log, so it's not the button that fires twice, but the error event.

like image 555
fancy Avatar asked May 06 '18 15:05

fancy


3 Answers

As mentioned in other answers, the problem is in React in DEV mode. In this mode it re-throws all exceptions to "improve debugging experience".

I see 4 different error scenarios

  1. Normal JS errors (for example, from an event handler, like in the question).

    These are sent to window.onerror twice by React's invokeGuardedCallbackDev.

  2. JS errors that happen during render, and there is no React's error boundary in the components tree.

    The same as scenario 1.

  3. JS errors that happen during render, and there is an error boundary somewhere in the components tree.

    These are sent to window.onerror once by invokeGuardedCallbackDev, but are also caught by the error boundary's componentDidCatch.

  4. JS errors inside promises, that were not handled.

    These aren't sent to window.onerror, but rather to window.onunhandledrejection. And that happens only once, so no problem with this.

My workaround

window.addEventListener('error', function (event) {
    const { error } = event;
    // Skip the first error, it is always irrelevant in the DEV mode.
    if (error.stack?.indexOf('invokeGuardedCallbackDev') >= 0 && !error.alreadySeen) {
        error.alreadySeen = true;
        event.preventDefault();
        return;
    }
    // Normal error handling.
}, { capture: true });
like image 196
Monsignor Avatar answered Oct 02 '22 06:10

Monsignor


It is known react error, related with implementation of error boundaries.

like image 7
Pavel Avatar answered Oct 22 '22 09:10

Pavel


I found a basic solution to this that should work in all scenarios.

It turns out that the object is identical in all calls, you could set up something to match them exactly, or you could just attach a custom attribute to the error object...

Admittedly this may only work with window.addEventListener('error', function...), as you are given the genuine error object as an argument, as opposed to window.onerror = function... which gets the data parts, such as message & lineNumber as opposed to the real error.

This is basically how I'm using it:

window.addEventListener('error', function (event) {
  if (event.error.hasBeenCaught !== undefined){
    return false
  }
  event.error.hasBeenCaught = true
  // ... your useful code here
})

If this is called with the same error twice it will exit before getting to your useful code, only executing the useful code once per error.

like image 6
Cameron Brown Avatar answered Oct 22 '22 08:10

Cameron Brown