New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error boundary seems to be failing to catch errors. #15571

Open
gunn opened this Issue Aug 20, 2017 · 47 comments

Comments

Projects
None yet
@gunn

gunn commented Aug 20, 2017

Environment

  1. react-native: 0.47.1
  2. react: 16.0.0-alpha.12
  3. node: v8.3.0
  • Target Platform:
    iOS, android

  • Build tools:
    create-react-native-app / expo locally, snack.expo.io online.

Steps to Reproduce

Use this code:

import React from 'react';
import { Text, Button } from 'react-native';

class ErrorBoundary extends React.Component {
  state = { hasError: false }

  componentDidCatch() {
    this.setState({ hasError: true })
  }

  render() {
    if (this.state.hasError) {
      return <Text>Error in Component</Text>
    }
    return this.props.children
  }
}

const Component = ()=> (
  <Button
    title="Throw Error!"
    onPress={()=> { throw new Error() }}
  />
)

export default ()=> (
  <ErrorBoundary>
    <Component />
  </ErrorBoundary>
)

Expected Behavior

It should behave like react in the browser and display the text "Error in Component".

Actual Behavior

In development mode the red error screen shows. In production the app crashes and restarts.

Reproducible Demo

https://snack.expo.io/ryHYYfPdZ

@chuckwu0

This comment has been minimized.

chuckwu0 commented Sep 25, 2017

I get the same problem

@luisfmsouza

This comment has been minimized.

luisfmsouza commented Sep 29, 2017

I had the same issue. Looking for any suggestion.

@tumanov-alex

This comment has been minimized.

tumanov-alex commented Oct 3, 2017

Same for me.

@clementdevosmc

This comment has been minimized.

clementdevosmc commented Oct 3, 2017

Same for me here.

@timwangdev

This comment has been minimized.

Collaborator

timwangdev commented Oct 4, 2017

I don't think componentDidCatch() method is available in react-native right now. React native is not rely on fiber in current version.

@eliperkins

This comment has been minimized.

Contributor

eliperkins commented Oct 4, 2017

componentDidCatch is available in RN 0.49.0-stable:

return void instance.componentDidCatch(capturedError.error, info);

This requires React 16.0.0-beta.5

@JesperLekland

This comment has been minimized.

JesperLekland commented Oct 17, 2017

@eliperkins My component still doesn't catch an error.

RN: 0.49.3
React: 16.0.0-beta.5

@eliperkins

This comment has been minimized.

Contributor

eliperkins commented Oct 17, 2017

@JesperLekland Can you show your component? Note that only errors thrown within React lifecycle events will be captured.

@wwwdata

This comment has been minimized.

wwwdata commented Oct 24, 2017

I also have this issue. I was just testing this by throwing an error in the componendDidUpdate lifecycle method of a child component. I can catch the error, but it also pops up as a red screen.

@tcdavis

This comment has been minimized.

tcdavis commented Oct 25, 2017

I can reproduce with
"react": "16.0.0-beta.5",
"react-native": "0.49.3"
and
"react": "16.0.0",
"react-native": "0.50.0-rc.0"

componentDidCatch appears to trigger / update state as expected, but a red box displaying the component stack is displayed.

import React, { Component } from 'react';
import { Text, View } from 'react-native';

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <Text>Error in Component</Text>;
    }
    return <BadComponent />;
  }
}

const BadComponent = () => {
  throw new Error();
};

export default class App extends Component<{}> {
  render() {
    return (
      <View>
        <Text>Welcome to React Native!</Text>
        <ErrorBoundary />
      </View>
    );
  }
}
@eliperkins

This comment has been minimized.

Contributor

eliperkins commented Oct 25, 2017

So is the "bug" here that the RedBox is displayed? It seems like if the error is caught and handled, there shouldn't be a RedBox, but I don't think that matches that the original issue was here...

@tcdavis

This comment has been minimized.

tcdavis commented Oct 25, 2017

That's all I'm seeing, at least. Happy to open a new issue if you'd prefer.

@gunn

This comment has been minimized.

gunn commented Oct 26, 2017

Here's the demo using expo SDK 22, which uses react 16.0.0-beta.5 and react-native 0.49:
https://snack.expo.io/Byc3n0A6-

Result: Uncaught Error: Error

@gusgard

This comment has been minimized.

Contributor

gusgard commented Nov 21, 2017

@gunn Try this example https://snack.expo.io/@gusgard/error-boundaries-(componentdidcatch-example)

Remember that errors are caught in render but are not caught in event handlers.

If you test it in release mode, you will see the message "Error in Component", otherwise in dev mode on your device (without using Expo) tap dismiss in the error screen.

@easybird

This comment has been minimized.

easybird commented Nov 27, 2017

I am having the same issue.
Error gets caught in componentDidCatch, but red screen is still shown - in DEV mode, and app crashes - in PROD mode.

  • "react": "^16.1.1",
  • "react-native": "^0.49.4",
@tcdavis

This comment has been minimized.

tcdavis commented Nov 28, 2017

@gusgard I believe it should handle errors in any component lifecycle methods, not just render: https://codepen.io/anon/pen/eePzKX
In your snack example the componentDidCatch isn't actually firing (on error the child components of the error boundary are still rendered) Snack is using a modified version of https://github.com/gaearon/react-transform-catch-errors to provide the redboxing you're seeing, and the error is handled before it bubbles up to the error boundary

@ide

This comment has been minimized.

Collaborator

ide commented Nov 28, 2017

@bvaughn Would you happen to know if the RN renderer is calling console.error() at error boundaries? This would explain the redbox in dev and crash in prod (which is what console.error does).

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Nov 28, 2017

Yes, console.error is called by default. The logic is here.

@ide

This comment has been minimized.

Collaborator

ide commented Nov 28, 2017

Thanks for the pointer, looks like we can override this with the injection interface.

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Nov 28, 2017

I'm sorry, @ides. I believe my previous message was misleading. (It's been a while since I've worked with this code.)

ReactNativeRenderer injects a custom implementation of showDialog here. That implementation comes from ReactNativeFiberErrorDialog, and it always returns false to prevent ReactFiberErrorLogger from console.error logging the error (due to this check).

So it's still possible that console.error is called in other places within the React Native renderer, but I don't think this is one of them. (You can search the renderer source for console.error references.) For example, if an error happens in the logCapturedError method, the renderer catches it and logs (via console.error).

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Nov 28, 2017

Actually, it looks like this call to ExceptionsManager.handleException will result in console.error being called. This is outside of the core "renderer" code so I'm less familiar with it. 😄

@Diwei-Chen

This comment has been minimized.

Diwei-Chen commented Dec 5, 2017

Same for me.

  • "react": "^16.0.0",
  • "react-native": "^0.50.4",
@stantoncbradley

This comment has been minimized.

stantoncbradley commented Dec 6, 2017

the issue is that onPress is not part of the React render lifecycle. Error boundaries will only trigger when React is trying to render your components:

render() {
  throw new Error('this will get caught');
}

or

render() {
  return (
    <Text>
      {undefined.methodDoesNotExist()} // NPE
    </Text>

hope that helps

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Dec 6, 2017

Cole's right. I didn't read the original bug report until now, but it's describing expected behavior. From the React docs:

Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Error boundaries do not catch errors for:

  • Event handlers (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)
@apolishch

This comment has been minimized.

apolishch commented Dec 11, 2017

This is still happening for us we are throwing from render and/or componentDidMount

To clarify. By "This" I mean that the red screen shows and the release build crashes, even though the componentDidCatch does actually run

Example: https://snack.expo.io/HJ4riE2ZG

@stantoncbradley

This comment has been minimized.

stantoncbradley commented Dec 11, 2017

@apolishch componentDidCatch doesn't prevent crashes, it just lets you catch the error and respond appropriately before the error goes unhandled. Per the example docs, set hasError to true in your error boundary component, and render a fallback ui if hasError is true. Right now, your error boundary is catching the error, but rendering the same error again

@mattcleary55

This comment has been minimized.

mattcleary55 commented Dec 12, 2017

I'm attempting to create a 'fail whale' page that pops up whenever the app crashes.

No matter what it is throwing a red screen.

Here is a snack I made with the same issues:
https://snack.expo.io/BynlDr6-z

What am I doing wrong here?

Thanks!

@stantoncbradley

This comment has been minimized.

stantoncbradley commented Dec 12, 2017

@judgejab your code looks fine. What happens when you dismiss the red screen. In dev red screen still shows, just dismiss. Run in release mode and it won't show red screen

@mattcleary55

This comment has been minimized.

mattcleary55 commented Dec 12, 2017

@stantoncbradley When I dismiss the red screen, I just get a fixed blank white screen.

In release mode the app just crashes.

@stantoncbradley

This comment has been minimized.

stantoncbradley commented Dec 12, 2017

@judgejab hmm code looks fine to me. What react/react native versions?

@mattcleary55

This comment has been minimized.

mattcleary55 commented Dec 12, 2017

I'm using React 16.2 and React Native 0.49. I'm currently going to work through updating React Native to see if anything changes.

That being said, why isn't the Expo Snack working? It should be the most up to date?

@shruti8597

This comment has been minimized.

shruti8597 commented Dec 27, 2017

It shows the red box with error as Cannot find variable abc.

render() 
  {   
       return (
    
 <View style={{flex:1,backgroundColor:white }}>
    <ScrollView style={{padding:10,flex: 1}}> 
        <ErrorBoundary>
                    
                    <Text>{abc}</Text>
     </ErrorBoundary>
    </ScrollView>
        </View>
);
 }


import React, { Component } from 'react';
import {
    View,
    Text
 } from 'react-native'

 export default class errorBoundary extends Component { 

 constructor(props) {

        super(props);
       this.state = {
          hasError:false,
  
         }     ;
    }

    componentDidCatch(eror,info) 
   {
         alert("ff");
        this.setState({ hasError: true });

    }

     render() 
     {   
          if (this.state.hasError) {
         // You can render any custom fallback UI
          return <Text>jbvi</Text>;
      }
       return this.props.children;

  }
 }
@tcdavis

This comment has been minimized.

tcdavis commented Jan 3, 2018

This is certainly resolved as of Expo 24 / RN 0.51: https://snack.expo.io/SyP0c39Xz
It may have been earlier, but I found the redbox behaviour on caught exceptions in dev pretty surprising.

For anyone else who runs into this, displaying a redbox does not mean that your error boundary isn't functioning. You can switch to prod (but lose all of the other dev goodies) or just dismiss the error and move on.

@outpunk

This comment has been minimized.

outpunk commented Jan 11, 2018

Same for me, react 16.2.0, react-native 0.51.0

@adrienthiery

This comment has been minimized.

Contributor

adrienthiery commented Feb 6, 2018

@outpunk @shruti8597 I thought I had the same issue, but it might just be a misunderstanding in the way Error boundaries are working.

Taking @shruti8597 's example :

// component.js
export default class TopLevelComponent extends Component {
    render() {
        return (
            <View style={{ flex: 1,
                backgroundColor: white }}
            >
                <ScrollView style={{ padding: 10,
                    flex: 1 }}
                >
                    <ErrorBoundary>
                        <Text>{abc}</Text>
                    </ErrorBoundary>
                </ScrollView>
            </View>
        );
    }
}

// ErrorBoundary.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';

export default class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false,
        };
    }

    componentDidCatch(eror, info) {
        this.setState({ hasError: true });
    }

    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return <Text>Oops</Text>;
        }

        return this.props.children;
    }
}

This indeed is not caught.

But modifying it to the following makes it work properly :

// component.js
export default class TopLevelComponent extends Component {
    render() {
        return (
            <View style={{ flex: 1,
                backgroundColor: white }}
            >
                <ScrollView style={{ padding: 10,
                    flex: 1 }}
                >
                    <ErrorBoundary>
                        <MyErroringComponent />
                    </ErrorBoundary>
                </ScrollView>
            </View>
        );
    }
}

class MyErroringComponent extends Component {
    render () {
        return (<Text>{abc}</Text>);
    }
}

// ErrorBoundary.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';

export default class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false,
        };
    }

    componentDidCatch(eror, info) {
        this.setState({ hasError: true });
    }

    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return <Text>Oops</Text>;
        }

        return this.props.children;
    }
}
@farzd

This comment has been minimized.

farzd commented Feb 15, 2018

@adrienthiery you're just forcing the state to render the error, the error needs to come from the actually wrapped child component. The whole idea is that the wrapper catches errors from the child component.

if you dismiss the red screen, you can see it working.
@stantoncbradley outside of dev mode [i.e in EXPO] results in this error expo/expo#916

@bvaughn this is still the case outside of event handler caveat, you can throw an error in the render function of the throwing component and the behaviour is still the same.

@haggholm

This comment has been minimized.

haggholm commented Mar 28, 2018

This is not working properly for me with react-native 0.54.3; the error boundary catches the error, but the red box is showing, and when dismissing the red box I get a blank white screen. The fallback UI does not show.

@succeed2011

This comment has been minimized.

succeed2011 commented Apr 8, 2018

after some tests, i found that if the error happens in child component componentDidCatch works(you should dismissing the red box), for example:

class Error extends Component{
    render(){
        throw new Error();
    }
}
export default class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false,
        };
    }

    componentDidCatch(eror, info) {
        this.setState({ hasError: true });
    }

    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return <Text>Oops</Text>;
        }

        return <Error />;
    }
}
@MrLoh

This comment has been minimized.

MrLoh commented May 28, 2018

I can't get this to work properly either. If I just run a test as @succeed2011 shows, it works, but if an error is thrown down the road somehwere in my real life app, the error isn't caught.

@MrLoh

This comment has been minimized.

MrLoh commented May 31, 2018

I found out why I couldn't catch deeper down errors. There was a bug in react-navigation@2.0.1 that was accidentally catching all errors in any react navigation screen, upgrading to 2.0.4 fixed it. Seems to me like error boundaries are working in the latest react native. 🎉

@rahamin1

This comment has been minimized.

rahamin1 commented Jul 13, 2018

@MrLoh Can you please post the package.json file that works for you?
And one more question: does it work when testing in dev-mode (e.g. on a genymotion emulator)? Or does it require dev-mode to be turned off?

@MrLoh

This comment has been minimized.

MrLoh commented Jul 14, 2018

I think it only works in production or staging environments
react-native: 0.55.4
react-navigation: 2.2.5

I just catches erros in lyfecycle and render, not in other global js code.

For that use this:

if (!__DEV__) {
	global.ErrorUtils.setGlobalHandler((error: Error, isFatal: boolean) => {
		// report the error that occurred and raise a modal if it was fatal
		reportError(error);
		if (isFatal) showErrorAlert(error);
	});
}
@Spenhouet

This comment has been minimized.

Spenhouet commented Aug 29, 2018

I also have problems with this.
From the replies in this thread: Do I understand correct that componentDidCatch is NOT supposed to catche errors that happen in a evet like handleSubmit from a child?
If so, which component do I need to use to also catche these errors?

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Aug 29, 2018

As the docs mention, componentDidCatch catchers errors that happen during the render phase– (this includes the constructor, render, and the componentWill/componentDid lifecycles)– but not in other JS code.

@MrLoh

This comment has been minimized.

MrLoh commented Aug 30, 2018

@Spenhouet you can’t catch those errors in a component, you have to use the global catching code I posted above.

@tomoyukim

This comment has been minimized.

tomoyukim commented Nov 6, 2018

I also face to redbox with componentDidCatch during development.
I get redbox even if componentDidCatch correctly handle the error. It seems redbox is shown by the following code:
https://github.com/facebook/react-native/blob/v0.57.3/Libraries/Renderer/oss/ReactNativeRenderer-dev.js#L12194
It's different context from global error handler and I couldn't suppress redbox with ErrorUtils.setGlobalHandler.

I can continue to work dismissing redbox but I'd like to suppress it when developing error handler.
Do you happen to have any idea to suppress it?

My react-native version is v0.57.3

@Kennytian

This comment has been minimized.

Kennytian commented Nov 6, 2018

Me too. show the redbox error, show componentDidCatch alert in my code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment