Closed longlho closed 7 years ago
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.
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?
@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.
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.
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?
https://github.com/facebook/react/issues/3313 has a really nice unit test that we should probably start using once the error boundaries issue is fixed.
:+1:
+1
+1 This makes debugging React apps much more difficult than it has to be!
+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.
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?
@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.
+1
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.
+1
+1
:+1:
@spicyj Should we label this as big-picture
?
I'm willing to but it doesn't require much discussion, it just needs to get done.
I'd be glad to help out with this. It's a pain point for my team.
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:
__DEV__===true
)<span>
(good for __DEV__===false
)However we are not sure about the correct way how to handle this. Feedback? Working on PR atm.
+1, this would be a huge improvement
+1, been thinking about this lately
very interested by this issue too.
Any idea on how the problem can be solved efficiently?
Let's leave this open until the new feature is complete.
What is the expected behavior here? Are we going to propagate the error to the top-most component?
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.
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.
+1, just got bit by this :)
Well, there's react-transform-catch-errors
for those using Babel, but I'd still like to see it fixed directly in React. :+1: from me.
+1
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?
@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.
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.
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
Can anyone point to an example of unstable_handleError
use in v15? I can't seem to figure out how to make it work :/
@thom-nic Officially, v15 does not support error boundaries. Unofficially, https://github.com/facebook/react/blob/master/src/core/__tests__/ReactErrorBoundaries-test.js
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.
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
Hi, @Aldredcz Thanks for your shearing. What is the licence of monkeypatching? Could I use it in my project?
@wangyangjun use it anywhere you wish :)
The updated link to unstable_handleError
in v15 is here:
https://github.com/facebook/react/blob/15-stable/src/renderers/shared/stack/reconciler/__tests__/ReactErrorBoundaries-test.js
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)
@Aldredcz It's really great work, thanks!
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.
+1
@bitmage That's true; if you implement unstable_handleError then React doesn't log anything or rethrow the error; it is up to you to do so. It is like a try/catch. In a future version of React we will log an error regardless though (see #8756 for a preview).
@spicyj are there cases where the unstable_handleError is not called?
I'm trying to build an HOC like that but the callback does not seem to be ever called:
import React from "react";
const RenderFallback = () => (
<div>error</div>
);
// See https://github.com/facebook/react/issues/2461
export const ErrorBoundaryConstructor = ({
fallbackComponent: RenderFallback,
onError = undefined,
} = {}) => (WrappedComponent) => {
return class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {error: false};
}
unstable_handleError(e) {
console.warn("unstable_handleError",e);
onError && onError(e);
this.setState({error: true});
}
render() {
const Comp = this.state.error ? fallbackComponent : WrappedComponent;
return <Comp {...this.props}/>;
}
}
};
export const DefaultErrorBoundary = Component => {
return ErrorBoundaryConstructor()(Component);
};
An example of client code:
import React from "react";
import Portal from "react-portal";
import classNames from "classnames";
import {DefaultErrorBoundary} from "utils/errorBoundary";
const FullscreenPortal = React.createClass({
render() {
return (
<Portal isOpened={true}
className={classNames(this.props.className, "fullscreen-portal")}
>
{this.props.children}
</Portal>
)
},
});
export default DefaultErrorBoundary(FullscreenPortal);
Where is your error thrown? I don't see it in your code.
So I'm trying to put some graceful error handling in case 1 of my views crap out:
However,
MyGoodView
does not get rendered w/ the following stack trace:Seems like error throw React in a bad state where
renderedComponent
isundefined
, thus cannot be unmounted. How do I handle this scenario?