Skip to content

Instantly share code, notes, and snippets.

@goldhand
Created August 19, 2017 21:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goldhand/4d74c214e7af7ff58b4479ea7623b686 to your computer and use it in GitHub Desktop.
Save goldhand/4d74c214e7af7ff58b4479ea7623b686 to your computer and use it in GitHub Desktop.
Don't ruin everything when a component fails to render
/**
* components/SafeRender.js
*/
import React, {Component, PropTypes} from 'react';
import {getDisplayName} from '../utils';
import {Alert} from '../generic';
import Translate from './Translate';
import logger from '../logger';
const TRANSLATE_RENDER_FALLBACK = 'There was a an Error.';
/**
* Default fallback for the SafeRender component
* @param {Object} props - props
* @param {Function} translate - translate
* @returns {Object} <RenderFallback/>
*/
const RenderFallback = ({
translate,
}) => <Alert level="error">{translate(TRANSLATE_RENDER_FALLBACK)}</Alert>;
RenderFallback.propTypes = {
translate: PropTypes.func.isRequired,
};
RenderFallback.displayName = 'RenderFallback';
/**
* Safely render a component using two error handling strategies
*
* The first strategy hijacks the decorated component's render method and wraps
* it inside a try / catch. It is used as a fallback incase the second strategy
* fails.
*
* The second strategy uses an experiemental React lifecycle method called
* `unstable_handleError` to try to handle render errors.
*
* @param {Component} [FallbackComponent] - Fallback react component displayed
* if render fails
* @param {function} [onError] - Will be passed the exception if render fails
* @returns {function} SafeRender decorator function
*/
export const SafeRenderConstructor = ({
FallbackComponent = Translate(RenderFallback),
onError = logger.warning,
} = {}) => WrappedComponent => {
/**
* Safely render a component in a try / catch
* @extends WrappedComponent
*/
class TryCatchRenderWrapper extends WrappedComponent {
static displayName = `TryCatchRender(${getDisplayName(WrappedComponent)})`;
render() {
try {
return super.render();
} catch (exception) {
if (typeof onError === 'function') onError(exception);
return <FallbackComponent />;
}
}
}
/**
* Safely render a component using the unstable_handleError lifecycle method
* @extends Component
*/
class HandleErrorRenderWrapper extends Component {
static displayName = `HandleErrorRender(${getDisplayName(WrappedComponent)})`;
constructor() {
super();
this.state = {error: false};
}
/* eslint-disable camelcase */
unstable_handleError(exception) {
this.setState({error: true});
if (typeof onError === 'function') onError(exception);
}
/* eslint-enable camelcase */
render() {
const {error} = this.state;
// Extend the TryCatchRenderWrapper
if (!error) return <TryCatchRenderWrapper {...this.props} />;
return <FallbackComponent />;
}
}
return HandleErrorRenderWrapper;
};
export default SafeRenderConstructor();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment