Error boundaries: Recover from errors thrown in `render` #2461

Open
longlho opened this Issue Nov 4, 2014 · 46 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
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
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
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.

@spicyj
Member
spicyj 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.

@spicyj spicyj 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
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
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

👍

@samzscott

+1

@moosingin3space

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

@jnak
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
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?

@spicyj
Member
spicyj 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.

@timbur
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 dmohs added a commit to dmohs/react-cljs that referenced this issue Aug 7, 2015
@dmohs dmohs 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.
853777e
@mhagmajer

+1

@nmartin413

+1

@aleclarson

👍

@gaearon
Member
gaearon commented Oct 19, 2015

@spicyj Should we label this as big-picture?

@jimfb jimfb added the big picture label Oct 19, 2015
@gaearon gaearon referenced this issue in gaearon/react-proxy Oct 19, 2015
Merged

Support React 0.14 #31

@spicyj
Member
spicyj commented Oct 19, 2015

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

@benmosher

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

@LeZuse
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.

@ngasull
ngasull commented Nov 24, 2015

+1, this would be a huge improvement

@yagudaev
yagudaev commented Dec 4, 2015

+1, been thinking about this lately

@slorber
Contributor
slorber commented Dec 11, 2015

very interested by this issue too.

Any idea on how the problem can be solved efficiently?

@jimfb jimfb closed this in #5602 Dec 16, 2015
@spicyj
Member
spicyj commented Dec 16, 2015

Let's leave this open until the new feature is complete.

@spicyj spicyj reopened this Dec 16, 2015
@mhagmajer

What is the expected behavior here? Are we going to propagate the error to the top-most component?

@spicyj
Member
spicyj commented Dec 29, 2015

An error bubbles up to the nearest error boundary (that is, a component with unstable_handleError defined). If that component errors when trying to handle the error, the error will keep rising.

@natew
natew commented Dec 29, 2015

Is there a PR to track for this? Just want to throw in my thanks on this as well, you all are killing it as usual.

@spicyj
Member
spicyj commented Dec 29, 2015

#5602 has the first code for this which catches errors only on initial render but not at any other time. When we have more I am sure that this issue number will be mentioned in the description so you will see them in the comment history here.

@cappslock

+1, just got bit by this :)

@glenjamin glenjamin referenced this issue in glenjamin/devboard Jan 24, 2016
Open

Better error handling? #17

@mik01aj
mik01aj commented Jan 27, 2016

Well, there's react-transform-catch-errors for those using Babel, but I'd still like to see it fixed directly in React. 👍 from me.

@joaomilho

+1

@DanielSundberg

My team just ran into this issue and found this thread.

Some comments in here hint that the react team is working on improving this...

Can anyone comment if there's any progress being made or if this perhaps is on the roadmap for 15.0 or a later release?

@jimfb
Contributor
jimfb commented Mar 14, 2016

@DanielSundberg Yes, basic/unstable support was added for initial render in v15, and support for updates is being added in a future release. It will be documented when it becomes stable.

@vguzev
vguzev commented Mar 16, 2016

For those who need some kind of error handling in render functions in React 13/14 here is the workaround. Just take the following Interceptor.jsx:

/**
 * Interceptor function that returns JSX-component in case of error.
 * Usage example:
 * render: interceptor('MyClass.myFunc', function() {
 *   ... Here is some code that potentially can generate Exception
 * }, <b>Hallelujah! Another soul saved!</b>)
 *
 * @param where - Name of the function - will be displayed in console when exception is caught
 * @param renderFunction - Function where exception can be raised.
 * @param component - Optional component which should be displayed in case of error.
 * @returns {Function}
 */
var Interceptor = function (where, renderFunction, component) {
    return function () {
        if (typeof Config != 'undefined' && Config.General && Config.General.stackTraceEnabled) {
            if (console) {
                if (console.debug) {
                    console.debug(where);
                }
                else {
                    console.log(where);
                }
            }
        }
        try {
            return renderFunction.apply(this, arguments);
        }
        catch (e) {
            console.error('Intercepted @ ' + where, e, this);
            if (component != null) {
                return component;
            }
            // Just redefine this logic here to return default custom error-handling component
            Core.Actions.InterfaceActions.crash();
            return null;
        }
    };
};

module.exports = Interceptor;

and use it like this:

var interceptor = require('Interceptor');
...
render: interceptor('ThisIsMyComponentName.render', function() {
  var a = b.c; // Raising exception...
  return <div>Hello world!</div>
})

Of course, this is just a workaround and the disadvantage of it is that you have to wrap all render functions with this interceptor in your project... but at least your app won't break on any tiny exception.

@chandlerprall

Any plan / possibility the unstable_handleError approach could be implemented to work in ReactDOM.renderToString? Currently React errors on var checkpoint = transaction.checkpoint(); in performInitialMountWithErrorHandling

@krukid krukid referenced this issue in erikras/react-redux-universal-hot-example Apr 29, 2016
Open

Server side render error handling broken #1086

@thom-nic
thom-nic commented May 7, 2016

Can anyone point to an example of unstable_handleError use in v15? I can't seem to figure out how to make it work :/

@jimfb
Contributor
jimfb commented May 7, 2016

@thom-nic Officially, v15 does not support error boundaries. Unofficially, https://github.com/facebook/react/blob/master/src/core/__tests__/ReactErrorBoundaries-test.js

@thom-nic
thom-nic commented May 7, 2016

Understood. Thanks for the example. Unfortunately I can't seem to get it working. I have the unstable_handleError defined in a root component with a console log statement but I don't see it called. Strange.

@Aldredcz
Aldredcz commented Jun 19, 2016 edited

Just wrote elegant (and global) workaround by monkeypatching React.createElement. Check out this gist if you are looking for a solution before this issue makes it to a release: https://gist.github.com/Aldredcz/4d63b0a9049b00f54439f8780be7f0d8
EDIT: also supporting Hot reload

@wangyangjun

Hi, @Aldredcz Thanks for your shearing. What is the licence of monkeypatching? Could I use it in my project?

@Aldredcz
Aldredcz commented Aug 2, 2016

@wangyangjun use it anywhere you wish :)

@ahmedyahiaalkaff

Hi, I get the following error when I try to use unstable_handleError:
[Error] TypeError: null is not an object (evaluating 'this._renderedComponent.unmountComponent')
(anonymous function) (comp.js, line 15788)

@danielcompton danielcompton referenced this issue in reagent-project/reagent Nov 6, 2016
Open

Handle thrown exceptions #272

@ivansglazunov

@Aldredcz It's really great work, thanks!

@bitmage
bitmage commented Jan 5, 2017

It seems as of React 15.0.2 the default behavior for performInitialMountWithErrorHandling is to swallow errors without any output. This makes debugging incredibly difficult. I don't know if it still works this way with more recent versions, but I would suggest a default of spitting out a warning at least. Anything that would tell the developer what just happened. Thanks!

FYI, as a workaround I implemented this in my top level component:

  unstable_handleError(...args) {
    console.error(...args)
  }

Not a terrible inconvenience, but how do you find this issue thread when you don't have an error to go off of? I had to run chrome with "pause on caught exceptions" to find it.

@bvaughn bvaughn added a commit that referenced this issue Jan 13, 2017
@bvaughn bvaughn Log all Fiber errors w/ component stack (#8756)
A new module has been added (ReactFiberErrorLogger). This logs error information (call stack and component stack) to the console to make errors easier to debug. It also prompts users to use error boundaries if they are not already using them.

In the future, perhaps this will be injectable, enabling users to provide their own handler for custom processing/logging. For the time being, this should help with issues like this / #2461.
be0de34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment