diff --git a/src/elements/common/error-boundary/DefaultError.js b/src/elements/common/error-boundary/DefaultError.js.flow similarity index 100% rename from src/elements/common/error-boundary/DefaultError.js rename to src/elements/common/error-boundary/DefaultError.js.flow diff --git a/src/elements/common/error-boundary/DefaultError.tsx b/src/elements/common/error-boundary/DefaultError.tsx new file mode 100644 index 0000000000..7126bcd8dd --- /dev/null +++ b/src/elements/common/error-boundary/DefaultError.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import ErrorMask from '../../../components/error-mask/ErrorMask'; +import messages from '../messages'; +import './DefaultError.scss'; + +export interface ErrorComponentProps { + error?: Error; +} + +const DefaultError = () => ( +
+ } + errorSubHeader={} + /> +
+); + +export default DefaultError; diff --git a/src/elements/common/error-boundary/ErrorBoundary.js b/src/elements/common/error-boundary/ErrorBoundary.js.flow similarity index 100% rename from src/elements/common/error-boundary/ErrorBoundary.js rename to src/elements/common/error-boundary/ErrorBoundary.js.flow diff --git a/src/elements/common/error-boundary/ErrorBoundary.tsx b/src/elements/common/error-boundary/ErrorBoundary.tsx new file mode 100644 index 0000000000..c37f3c7802 --- /dev/null +++ b/src/elements/common/error-boundary/ErrorBoundary.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import noop from 'lodash/noop'; +import DefaultError, { ErrorComponentProps } from './DefaultError'; +import { ERROR_CODE_UNEXPECTED_EXCEPTION, IS_ERROR_DISPLAYED } from '../../../constants'; +import type { ElementsXhrError, ElementsError } from '../../../common/types/api'; +import type { ElementOrigin } from '../flowTypes'; + +export interface ErrorBoundaryProps { + children: React.ReactElement; + errorComponent: React.ComponentType; + errorOrigin: ElementOrigin; + onError: (error: ElementsError) => void; +} + +type State = { + error?: Error; +}; + +class ErrorBoundary extends React.Component { + static defaultProps = { + errorComponent: DefaultError, + onError: noop, + }; + + state: State = {}; + + componentDidCatch(error: Error, info: React.ErrorInfo): void { + this.setState({ error }, () => { + this.handleError( + error, + ERROR_CODE_UNEXPECTED_EXCEPTION, + { + ...info, + }, + this.props.errorOrigin, + ); + }); + } + + /** + * Formats the error and emits it to the top level onError prop + * + * @param error - the error which occurred + * @param code - the error code to identify what error occurred + * @param contextInfo - additional information which may be useful for the consumer of the error + * @param origin - the origin of the error + * @return void + */ + handleError = ( + error: ElementsXhrError | Error, + code: string, + contextInfo: Record = {}, + origin: ElementOrigin = this.props.errorOrigin, + ): void => { + if (!error || !code || !origin) { + return; + } + + const elementsError: ElementsError = { + type: 'error', + code, + message: error.message, + origin, + context_info: { + [IS_ERROR_DISPLAYED]: true, + ...contextInfo, + }, + }; + + this.props.onError(elementsError); + }; + + render() { + const { children, errorComponent: ErrorComponent, ...rest } = this.props; + const { error } = this.state; + if (error) { + return ; + } + + return React.cloneElement(children, { + ...rest, + onError: this.handleError, + }); + } +} + +export default ErrorBoundary; diff --git a/src/elements/common/error-boundary/index.js b/src/elements/common/error-boundary/index.js.flow similarity index 100% rename from src/elements/common/error-boundary/index.js rename to src/elements/common/error-boundary/index.js.flow diff --git a/src/elements/common/error-boundary/index.ts b/src/elements/common/error-boundary/index.ts new file mode 100644 index 0000000000..854a5a9100 --- /dev/null +++ b/src/elements/common/error-boundary/index.ts @@ -0,0 +1,2 @@ +export { default } from './ErrorBoundary'; +export { default as withErrorBoundary } from './withErrorBoundary'; diff --git a/src/elements/common/error-boundary/withErrorBoundary.js b/src/elements/common/error-boundary/withErrorBoundary.js.flow similarity index 100% rename from src/elements/common/error-boundary/withErrorBoundary.js rename to src/elements/common/error-boundary/withErrorBoundary.js.flow diff --git a/src/elements/common/error-boundary/withErrorBoundary.tsx b/src/elements/common/error-boundary/withErrorBoundary.tsx new file mode 100644 index 0000000000..8777f6e217 --- /dev/null +++ b/src/elements/common/error-boundary/withErrorBoundary.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import DefaultError, { ErrorComponentProps } from './DefaultError'; +import ErrorBoundary from './ErrorBoundary'; +import type { ElementOrigin } from '../flowTypes'; + +type ComponentWithRef = React.ComponentType

>; + +const withErrorBoundary = + (errorOrigin: ElementOrigin, errorComponent: React.ComponentType = DefaultError) => +

(WrappedComponent: ComponentWithRef) => { + return React.forwardRef((props: P, ref: React.Ref) => ( + )} + > + + + )); + }; + +export default withErrorBoundary;