feat(error-handling): introduce unified and configurable error handling (#7761)

Refs #7778
This commit is contained in:
Vladimir Gorej
2022-01-24 16:12:13 +01:00
committed by GitHub
parent 4f2287fe53
commit 8b1c4a7c1a
19 changed files with 629 additions and 295 deletions

View 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

View 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

View 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
}

View 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