diff --git a/src/core/plugins/view/root-injects.jsx b/src/core/plugins/view/root-injects.jsx index 27d4379f..7fdb8fe0 100644 --- a/src/core/plugins/view/root-injects.jsx +++ b/src/core/plugins/view/root-injects.jsx @@ -1,4 +1,5 @@ import React, { Component } from "react" +import PropTypes from "prop-types" import ReactDOM from "react-dom" import { connect, Provider } from "react-redux" import omit from "lodash/omit" @@ -69,30 +70,71 @@ export const render = (getSystem, getStore, getComponent, getComponents, domNode ReactDOM.render(( ), domNode) } +class ErrorBoundary extends Component { + constructor(props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error) { + return { hasError: true, error } + } + + componentDidCatch(error, errorInfo) { + console.error(error, errorInfo) // eslint-disable-line no-console + } + + render() { + if (this.state.hasError) { + return + } + + return this.props.children + } +} +ErrorBoundary.propTypes = { + targetName: PropTypes.string, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + ]) +} +ErrorBoundary.defaultProps = { + targetName: "this component", + children: null, +} + +const Fallback = ({ name }) => ( +
+ 😱 Could not render { name === "t" ? "this component" : name }, see the console. +
+) +Fallback.propTypes = { + name: PropTypes.string.isRequired, +} + // Render try/catch wrapper const createClass = OriginalComponent => class extends Component { render() { - return + return ( + + + + ) } } -const Fallback = ({ - name // eslint-disable-line react/prop-types -}) =>
😱 Could not render { name === "t" ? "this component" : name }, see the console.
- const wrapRender = (component) => { const isStateless = component => !(component.prototype && component.prototype.isReactComponent) - const target = isStateless(component) ? createClass(component) : component - - const ori = target.prototype.render + const { render: oriRender} = target.prototype target.prototype.render = function render(...args) { try { - return ori.apply(this, args) + return oriRender.apply(this, args) } catch (error) { console.error(error) // eslint-disable-line no-console - return + return } } diff --git a/test/unit/core/system/system.jsx b/test/unit/core/system/system.jsx index e5082726..43ded037 100644 --- a/test/unit/core/system/system.jsx +++ b/test/unit/core/system/system.jsx @@ -1,12 +1,11 @@ - import React, { PureComponent } from "react" +import { fromJS } from "immutable" +import { render, mount } from "enzyme" +import { Provider } from "react-redux" import System from "core/system" -import { fromJS } from "immutable" -import { render } from "enzyme" import ViewPlugin from "core/plugins/view/index.js" import filterPlugin from "core/plugins/filter/index.js" -import { connect, Provider } from "react-redux" describe("bound system", function(){ @@ -704,7 +703,7 @@ describe("bound system", function(){ describe("rootInjects", function() { it("should attach a rootInject function as an instance method", function() { // This is the same thing as the `afterLoad` tests, but is here for posterity - + // Given const system = new System({ plugins: [ @@ -985,10 +984,9 @@ describe("bound system", function(){ }) // When - let Component = system.getSystem().getComponent("BrokenComponent") - const renderedComponent = render() + const Component = system.getSystem().getComponent("BrokenComponent") + const renderedComponent = mount() - // Then expect(renderedComponent.text().startsWith("😱 Could not render")).toEqual(true) })