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 boundaries: Recover from errors thrown in `render` #2461

Closed
longlho opened this Issue Nov 4, 2014 · 85 comments

Comments

Projects
None yet
@longlho

longlho commented Nov 4, 2014

So I'm trying to put some graceful error handling in case 1 of my views crap out:

var MyGoodView = React.createClass({
  render: function () {
    return <p>Cool</p>;
  }
});

var MyBadView = React.createClass({
  render: function () {
    throw new Error('crap');
  }
});

try {
  React.render(<MyBadView/>, document.body);
} catch (e) {
  React.render(<MyGoodView/>, document.body);
}

However, MyGoodView does not get rendered w/ the following stack trace:

stack trace

Seems like error throw React in a bad state where renderedComponent is undefined, thus cannot be unmounted. How do I handle this scenario?

@syranide

This comment has been minimized.

Contributor

syranide commented Nov 4, 2014

There are quite a few places where you can't throw exceptions without React ending up in an invalid state. If you can't trust your developers then the only solution I know of is to put a try/catch in each render.

@longlho

This comment has been minimized.

longlho commented Nov 4, 2014

Ah thanks :) I mean it's not a matter of trust it's to prevent using 3rd-party React component and whatnot. Do we plan to have more support for error handling?

@syranide

This comment has been minimized.

Contributor

syranide commented Nov 4, 2014

@longlho Not my area, but I believe the two biggest blockers are; many browsers ruin the error/call-stack when rethrowing and try-catch incurs performance penalities and these are hot paths.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Nov 4, 2014

Yeah, you may hear conversation about "error boundaries" which means making a way to handle this sort of thing more gracefully and something we want to find a good solution to. I don't think we have a tracking issue for it though so let's use this one.

@sophiebits sophiebits changed the title from Can't recover from error in `render` function to Error boundaries: Recover from errors thrown in `render` Nov 4, 2014

@longlho

This comment has been minimized.

longlho commented Nov 6, 2014

Ah thanks guys :) this does sound like a big issue but definitely important. Given that react is stateful wouldn't it require some sort of sandbox for rendering to make sure everything's cool, then apply the new state I guess? Is there any active effort on this front?

@jimfb

This comment has been minimized.

Contributor

jimfb commented Mar 5, 2015

#3313 has a really nice unit test that we should probably start using once the error boundaries issue is fixed.

@pedroteixeira

This comment has been minimized.

pedroteixeira commented Mar 16, 2015

👍

1 similar comment
@samzscott

This comment has been minimized.

samzscott commented Jun 11, 2015

+1

@moosingin3space

This comment has been minimized.

moosingin3space commented Jun 11, 2015

+1 This makes debugging React apps much more difficult than it has to be!

@jnak

This comment has been minimized.

jnak commented Jun 11, 2015

+1 It's very scary that one child component error can take an entire component tree down. It would be awesome to put try/catch statements at some key components in that tree.

@skiano

This comment has been minimized.

skiano commented Jun 22, 2015

I am working on an isomorphic app with many components, and we lean on very messy third-party APIs. So there is a relatively high risk that a developer can write a new component that makes bad assumptions about the data it receives (since we render server side, this destroys the request and the user cannot get any part of the page)

We decided to take an aggressive approach to the problem by wrapping React.createElement in our app and replacing the lifecycle methods with our own error handling. For ES6 we were thinking of doing the same thing by extending the Component class

Even though this leaves the app in an invalid state we thought it was preferable to rendering nothing for the user.

Here is a link to the code I am working on:
https://github.com/skiano/react-safe-render/blob/feature/safe-methods/index.js

Is there some reason why I really should not do this?

@sophiebits

This comment has been minimized.

Member

sophiebits commented Jun 22, 2015

@skiano If that's working for you, no reason to stop. Our eventual solution should be a little more flexible than this and more efficient but will be similar in spirit.

@conoremclaughlin

This comment has been minimized.

conoremclaughlin commented Jul 8, 2015

+1

@timbur

This comment has been minimized.

timbur commented Jul 22, 2015

Any idea what the time-frame might be for an official solution? Just curious. :) In the mean-time I will probably just monkey-patch everything.

dmohs added a commit to dmohs/react-cljs that referenced this issue Aug 7, 2015

Workaround for facebook/react#2461
If a component's render function throws an error, React cannot recover. This causes subsequent hot reloads to fail. Now we catch these errors during hot reloading so things are moar better.
@mhagmajer

This comment has been minimized.

mhagmajer commented Sep 11, 2015

+1

@nmartin413

This comment has been minimized.

nmartin413 commented Oct 14, 2015

+1

1 similar comment
@aleclarson

This comment has been minimized.

aleclarson commented Oct 18, 2015

👍

@gaearon

This comment has been minimized.

Member

gaearon commented Oct 19, 2015

@spicyj Should we label this as big-picture?

@sophiebits

This comment has been minimized.

Member

sophiebits commented Oct 19, 2015

I'm willing to but it doesn't require much discussion, it just needs to get done.

@benmosher

This comment has been minimized.

benmosher commented Oct 28, 2015

I'd be glad to help out with this. It's a pain point for my team.

@LeZuse

This comment has been minimized.

LeZuse commented Nov 22, 2015

Hi, first of all the test case in the first post works only within certain app context. Here's the working test case http://jsfiddle.net/XeeD/pLqphjm4/.

There is an already mounted component, that will throw in render() for the second render pass which leaves React in an inconsistent state, breaking the code here in ReactReconciler.unmountComponent

We thought about a few approaches by catching it here in ReactCompositeComponentMixin._renderValidatedComponent and:

  • render an error string instead of the component (might be good for __DEV__===true)
  • render an empty <span> (good for __DEV__===false)
  • log the thrown error into the console (invariant)

However we are not sure about the correct way how to handle this. Feedback? Working on PR atm.

@joshclowater

This comment has been minimized.

joshclowater commented May 23, 2017

Why the 👎s for @DanielSundberg? His implementation is the only one I can get to work with component classes.

@DouglasLivingstone

This comment has been minimized.

DouglasLivingstone commented May 24, 2017

I've not voted on it myself, but I wouldn't use it since it changes which method all the derived classes need to override to render, so special-case knowledge about how error handling in the base class works propagates to affect all React components. The current workaround I'm using is to wrap the render method in the base class in the constructor by calling trapRenderExceptions, which is very hacky but requires no changes to how the derived classes need to be written, so at least constrains the impact of the hack:

function trapRenderExceptions(component) {
    var oldRender = component.render;
    component.render = () => {
        try {
            return oldRender.call(component);
        }
        catch (e) {
            console.error("Serious error: Exception thrown in render. Report as a bug.");
            console.error(e);
            return null;
        }
    };
}

Very much looking forward to v16 with proper error boundaries!

@bywo

This comment has been minimized.

bywo commented Jun 12, 2017

We've used the BatchingStrategy API as a way to wrap try/catches around React code. It catches errors in render()s (initial and updates) and catches errors in your event handlers/callbacks. It doesn't require any monkey-patching and doesn't require you to wrap or modify any of your existing components.

HOWEVER, the BatchingStrategy API is going to be deprecated, so this strategy won't work with React 16. We've used it successfully in React 0.13 through 15.5, and I think it's a useful solution until error boundaries are ready.

More details here: https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 26, 2017

Small update on this: latest alphas of React (react@next and react-dom@next) are still not ready for wide use (there's known issues with infinite loops in development), but they implement error boundaries so you can start playing with them (and see if they satisfy your use case).

The recommended way to use them is to create a component like this:

class ErrorBoundary extends React.Component {
  state = { error: null };

  unstable_handleError(error, info) {
    this.setState({ error });
    // Note: you can send error and info to analytics if you want too
  }

  render() {
    if (this.state.error) {
      // You can render anything you want here,
      // but note that if you try to render the original children
      // and they throw again, the error boundary will no longer catch that.
      return <h1>Something went wrong: {this.state.error.toString()}.</h1>;
    }
    return this.props.children;
  }
}

Then you can put it anywhere:

<ErrorBoundary>
  <MyApp />
</ErrorBoundary>

or to protect specific components:

<Layout
  left={<Sidebar />}
  right={<ErrorBoundary><RouteHandler /></ErrorBoundary>}
/>

Note that ErrorBoundary acts as a "catch" block. It only "catches" errors in its children, but not in itself. You can nest error boundaries (just like you can nest catch blocks).

The exact API may change in 16.

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 26, 2017

@slorber If error boundaries don't work in latest alpha for some reason please create a complete reproducing example.

@kossnocorp

This comment has been minimized.

kossnocorp commented Jun 26, 2017

ErrorBoundary seems interesting, however, how it would allow rescuing individual components instead of a (sub)tree? In the examples above when a small icon throws an error during render, the whole app/page won't be rendered at all.

I'm imagining something like that:

ReactDOM.safeRender(
  <App />,
  document.getElementById('root'),
  (err, componentInstance) => {
    // If component fails then report the incident and render nothing
    reportError(err, componentInstance)
    return null
  }
)

...or at least on a class level, so that we could use inheritance.

class SafeComponent extends Component {
  catchRender (err) {
    reportError(err, this)
    return null
  }
}

class Whatever extends SafeComponent {
  render () {
    throw 'whoops'
    return <div>A small cosmetic detail</div>
  }
}
@gaearon

This comment has been minimized.

Member

gaearon commented Jun 26, 2017

There is no support for doing this at individual component level (randomly disappearing individual components isn't a great user experience). You are, however, free to wrap all your components with an error boundary, or to create a base class (although we generally don't recommend that).

In the examples above when a small icon throws an error during render, the whole app/page won't be rendered at all.

Why a whole app/page? You can divide your page into individual sections and wrap each of those with error boundary. The granularity is up to you.

@natew

This comment has been minimized.

natew commented Jun 26, 2017

@gaearon

This comment has been minimized.

Member

gaearon commented Jun 26, 2017

That warrants a separate discussion (it's not immediately clear to me how hot reloading should work with error handling). We might be looking at hot reloading some time this year, and this is a good time to discuss the ideal experience.

@joshclowater

This comment has been minimized.

joshclowater commented Jun 27, 2017

I ended up implementing ErrorBoundary as a higher order component to wrap my components with.

export default function ErrorBoundary(WrappedComponent) {
  class ErrorBoundaryComponent extends React.Component {
    state = { error: null };

    unstable_handleError(error, info) {
      console.error(error, info);
      this.setState({ error });
      // Note: you can send error and info to analytics if you want too
    }

    render() {
      if (this.state.error) {
        return null;
      }
      return <WrappedComponent {...this.props} />;
    }
  };
  return ErrorBoundaryComponent;
}

This solution is considerably better than extending a base component because it can wrap functional components

function FunctionalComponent(props) {
  ...
}
export default ErrorBoundary(FunctionalComponent);

and catch errors that occur in the component's other wrappers, such as a selector in redux's mapStateToProps.

export default ErrorBoundary(connect(mapStateToProps, mapDispatchToProps)(SomeComponent));
@gaearon

This comment has been minimized.

Member

gaearon commented Jun 27, 2017

@joshclowater What is the benefit as opposed to using a regular component?

<ErrorBoundary>
  <Something />
</ErrorBoundary>

This gives you more freedom with granularity.

@joshclowater

This comment has been minimized.

joshclowater commented Jun 27, 2017

@gaearon Personal preference I guess. I find it slightly cleaner as it abstracts the error handling out of the JSX. If I'm wrapping many components, it could noisy to parse through all of the <ErrorBoundary>s. Also, if I know that's how I want the component to handle errors, then I don't have to worry about the parent containers wrapping it with <ErrorBoundary>...<ErrorBoundary> and risk missing it somewhere else.

<ErrorBoundary>
  <Something />
</ErrorBoundary>
<ErrorBoundary>
  <Something />
</ErrorBoundary>

...

<Something /> // Oops missed that one
@jedwards1211

This comment has been minimized.

Contributor

jedwards1211 commented Jul 7, 2017

Every time I get an error in a render method I have to scroll up through dozens of errors from React being in a broken state to find the error from my code that actually blew it up. That's the biggest difficulty it causes me. I sort of wish React would could just stop dead and not even throw any more errors, since it would be easier for me to find the root cause, and it's not really in a viable state anyway.

react-hot-loader v3 doesn't seem to manage to wrap all of my functional stateless components... it would be really helpful if there were a supported way to guarantee a dev tool (i.e. in dev mode if not prod) would show a redbox if any rendering throws an error.

@jedwards1211

This comment has been minimized.

Contributor

jedwards1211 commented Jul 7, 2017

Perhaps concern about the try-catch performance penalty will soon be obsolete: https://stackoverflow.com/a/19728876/200224

Not only does one comment mention that recent versions of V8 can optimize functions containing try-catch blocks, that answer explains that most of the performance penalty can be avoided by moving the try-catch into a wrapper function so that the main function which could throw can be optimized.

From Petka Antonov's optimization killers guide:

To use such statements with minimal impact, they must be isolated to a minimal function so that the main code is not affected:

var errorObject = {value: null};
function tryCatch(fn, ctx, args) {
    try {
        return fn.apply(ctx, args);
    }
    catch(e) {
        errorObject.value = e;
        return errorObject;
    }
}

var result = tryCatch(mightThrow, void 0, [1,2,3]);
//Unambiguously tells whether the call threw
if(result === errorObject) {
    var error = errorObject.value;
}
else {
    //result is the returned value
}
@gaearon

This comment has been minimized.

Member

gaearon commented Jul 7, 2017

@jedwards1211

That's the biggest difficulty it causes me. I sort of wish React would could just stop dead and not even throw any more errors, since it would be easier for me to find the root cause, and it's not really in a viable state anyway.

Yes, and this is exactly what we’re doing in React 16. 😉

react-hot-loader v3 doesn't seem to manage to wrap all of my functional stateless components... it would be really helpful if there were a supported way to guarantee a dev tool (i.e. in dev mode if not prod) would show a redbox if any rendering throws an error.

Please read my comment above: #2461 (comment). This is pretty much what we’ve added.

@jedwards1211

This comment has been minimized.

Contributor

jedwards1211 commented Jul 7, 2017

@gaearon oh, I see. That will be nice, though I assume I will probably end up using react-guard (which I just discovered from this thread) more than something like <ErrorBoundary> for dev mode.

@jedwards1211

This comment has been minimized.

Contributor

jedwards1211 commented Jul 7, 2017

...nevermind, I thought react-guard made it possible to display a redbox in place of the component that failed, but it appears to display the redbox in place of the root component.

@gaearon

This comment has been minimized.

Member

gaearon commented Jul 7, 2017

Error boundaries don't have to be at the root, as I mentioned before. You can put them in a few strategic places (e.g. route handler wrapper, and important components big enough to show a useful box).

I don't think it's a good idea to show them in place of every individual components. Components can be offscreen, or too little to show the box. In fact in DEV I'd probably always want to see a fullscreen error.

We still haven't decided what's the default way to show error boxes in React 16. @bvaughn was prototyping this at some point. We'll get back to it before the release.

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Jul 8, 2017

👍 I look forward to exploring this a bit more, hopefully soon

@btnwtn

This comment has been minimized.

btnwtn commented Jul 17, 2017

Is unstable_handleError going to be the official name of the method in React 16?

@gaearon

This comment has been minimized.

Member

gaearon commented Jul 17, 2017

No. We settled on componentDidCatch.

@gaearon gaearon marked this as a duplicate of #10215 Jul 19, 2017

@ivansglazunov

This comment has been minimized.

ivansglazunov commented Jul 25, 2017

@gaearon componentDidCatch just for current component or for childs too?
In the case of an unknown source of components, for the possibility of dismantling, it is important to catch children from parents, too.

@gaearon

This comment has been minimized.

Member

gaearon commented Jul 25, 2017

It catches for the whole subtree (but not for the component itself).

@bvaughn bvaughn referenced this issue Jul 26, 2017

Closed

React 16 RC #10294

@gaearon

This comment has been minimized.

Member

gaearon commented Jul 26, 2017

Catching errors in child components is now supported.
You can use the new componentDidCatch() hook in React 16 Beta 1 which you can try now.

You can read more about this feature in our new blog post: Error Handling in React 16.

I think we can close this issue. It is likely the feature will evolve over time, and we’d love your feedback, but let’s discuss it in more focused future issues.

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