feat(error-handling): introduce unified and configurable error handling (#7761)
Refs #7778
This commit is contained in:
50
src/core/plugins/safe-render/components/error-boundary.jsx
Normal file
50
src/core/plugins/safe-render/components/error-boundary.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import PropTypes from "prop-types"
|
||||
import React, { Component } from "react"
|
||||
|
||||
import { componentDidCatch } from "../fn"
|
||||
import Fallback from "./fallback"
|
||||
|
||||
export class ErrorBoundary extends Component {
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.state = { hasError: false, error: null }
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
this.props.fn.componentDidCatch(error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getComponent, targetName, children } = this.props
|
||||
|
||||
if (this.state.hasError) {
|
||||
const FallbackComponent = getComponent("Fallback")
|
||||
return <FallbackComponent name={targetName} />
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
}
|
||||
ErrorBoundary.propTypes = {
|
||||
targetName: PropTypes.string,
|
||||
getComponent: PropTypes.func,
|
||||
fn: PropTypes.object,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
])
|
||||
}
|
||||
ErrorBoundary.defaultProps = {
|
||||
targetName: "this component",
|
||||
getComponent: () => Fallback,
|
||||
fn: {
|
||||
componentDidCatch,
|
||||
},
|
||||
children: null,
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
13
src/core/plugins/safe-render/components/fallback.jsx
Normal file
13
src/core/plugins/safe-render/components/fallback.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
const Fallback = ({ name }) => (
|
||||
<div className="fallback">
|
||||
😱 <i>Could not render { name === "t" ? "this component" : name }, see the console.</i>
|
||||
</div>
|
||||
)
|
||||
Fallback.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default Fallback
|
||||
32
src/core/plugins/safe-render/fn.jsx
Normal file
32
src/core/plugins/safe-render/fn.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { Component } from "react"
|
||||
|
||||
export const componentDidCatch = console.error
|
||||
|
||||
const isClassComponent = component => component.prototype && component.prototype.isReactComponent
|
||||
|
||||
export const withErrorBoundary = (getSystem) => (WrappedComponent) => {
|
||||
const { getComponent, fn } = getSystem()
|
||||
const ErrorBoundary = getComponent("ErrorBoundary")
|
||||
const targetName = fn.getDisplayName(WrappedComponent)
|
||||
|
||||
class WithErrorBoundary extends Component {
|
||||
render() {
|
||||
return (
|
||||
<ErrorBoundary targetName={targetName} getComponent={getComponent} fn={fn}>
|
||||
<WrappedComponent {...this.props} {...this.context} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
}
|
||||
WithErrorBoundary.displayName = `WithErrorBoundary(${targetName})`
|
||||
if (isClassComponent(WrappedComponent)) {
|
||||
/**
|
||||
* We need to handle case of class components defining a `mapStateToProps` public method.
|
||||
* Components with `mapStateToProps` public method cannot be wrapped.
|
||||
*/
|
||||
WithErrorBoundary.prototype.mapStateToProps = WrappedComponent.prototype.mapStateToProps
|
||||
}
|
||||
|
||||
return WithErrorBoundary
|
||||
}
|
||||
|
||||
42
src/core/plugins/safe-render/index.js
Normal file
42
src/core/plugins/safe-render/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import zipObject from "lodash/zipObject"
|
||||
|
||||
import ErrorBoundary from "./components/error-boundary"
|
||||
import Fallback from "./components/fallback"
|
||||
import { componentDidCatch, withErrorBoundary } from "./fn"
|
||||
|
||||
const safeRenderPlugin = ({componentList = [], fullOverride = false} = {}) => ({ getSystem }) => {
|
||||
const defaultComponentList = [
|
||||
"App",
|
||||
"BaseLayout",
|
||||
"VersionPragmaFilter",
|
||||
"InfoContainer",
|
||||
"ServersContainer",
|
||||
"SchemesContainer",
|
||||
"AuthorizeBtnContainer",
|
||||
"FilterContainer",
|
||||
"Operations",
|
||||
"OperationContainer",
|
||||
"parameters",
|
||||
"responses",
|
||||
"OperationServers",
|
||||
"Models",
|
||||
"ModelWrapper",
|
||||
]
|
||||
const mergedComponentList = fullOverride ? componentList : [...defaultComponentList, ...componentList]
|
||||
const wrapFactory = (Original, { fn }) => fn.withErrorBoundary(Original)
|
||||
const wrapComponents = zipObject(mergedComponentList, Array(mergedComponentList.length).fill(wrapFactory))
|
||||
|
||||
return {
|
||||
fn: {
|
||||
componentDidCatch,
|
||||
withErrorBoundary: withErrorBoundary(getSystem),
|
||||
},
|
||||
components: {
|
||||
ErrorBoundary,
|
||||
Fallback,
|
||||
},
|
||||
wrapComponents,
|
||||
}
|
||||
}
|
||||
|
||||
export default safeRenderPlugin
|
||||
Reference in New Issue
Block a user