Created
August 19, 2017 21:45
-
-
Save goldhand/4d74c214e7af7ff58b4479ea7623b686 to your computer and use it in GitHub Desktop.
Don't ruin everything when a component fails to render
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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