- This code handles any JS runtime error during rendering React components. Without this handling, once an error occurs, whole component tree is damaged and can't be used at all. With this handling, nothing will be rendered in production environment (error span in dev env.) + in production the error is logged to Sentry (if you are not using it just delete related code)
- This is basicaly a workaround for proposed feature in React core - described in Issue: facebook/react#2461
- Works for all variants of Component creation -
React.createClass
, extending React.Component
and also stateless functional components.
- To get this work, just put this snippet into your entry js file. Then it will work in whole application.
- Also supporting React Hot Reload!
- If you find this useful, please retweet https://twitter.com/Aldredcz/status/744650159942995968 :)
- specify custom error renderer (global / per component, e.g. by implementing method
renderOnError()
in a component)
import React from 'react';
const statelessComponentsMap = new Map(); // original -> monkeypatched stateless functional components cache
let errorPlaceholder = <noscript/>;
if (__DEV__) {
errorPlaceholder = (
<span
style={{
background: 'red',
color: 'white'
}}
>
Render error!
</span>
);
}
function logError(Component, error) {
const errorMsg = `Error while rendering component. Check render() method of component '${Component.displayName || Component.name || '[unidentified]'}'.`;
console.error(errorMsg, 'Error details:', error); // eslint-disable-line
if (typeof Raven !== 'undefined' && typeof Raven.captureException === 'function') {
Raven.captureException(new Error(errorMsg), {
extra: {
errorStack: error.stack
}
});
}
}
function monkeypatchRender(prototype) {
if (prototype && prototype.render && !prototype.render.__handlingErrors) {
const originalRender = prototype.render;
prototype.render = function monkeypatchedRender() {
try {
return originalRender.call(this);
} catch (error) {
logError(prototype.constructor, error);
return errorPlaceholder;
}
};
prototype.render.__handlingErrors = true; // flag render method so it's not wrapped multiple times
}
}
const originalCreateElement = React.createElement;
React.createElement = (Component, ...rest) => {
if (typeof Component === 'function') {
if (Component.prototype && typeof Component.prototype.render === 'function') {
monkeypatchRender(Component.prototype);
}
// stateless functional component
if (!Component.prototype || !Component.prototype.render) {
const originalStatelessComponent = Component;
if (statelessComponentsMap.has(originalStatelessComponent)) { // load from cache
Component = statelessComponentsMap.get(originalStatelessComponent);
} else {
Component = (...args) => {
try {
return originalStatelessComponent(...args);
} catch (error) {
logError(originalStatelessComponent, error);
return errorPlaceholder;
}
};
Object.assign(Component, originalStatelessComponent); // copy all properties like propTypes, defaultProps etc.
statelessComponentsMap.set(originalStatelessComponent, Component); // save to cache, so we don't generate new monkeypatched functions every time.
}
}
}
return originalCreateElement.call(React, Component, ...rest);
};
// allowing hot reload
const originalForceUpdate = React.Component.prototype.forceUpdate;
React.Component.prototype.forceUpdate = function monkeypatchedForceUpdate() {
monkeypatchRender(this);
originalForceUpdate.call(this);
};
@Psykar thanks for the solution, I haven't used stateful components inside stateless ones to be honest.
I will integrate it into the gist :)