Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/elements/common/error-boundary/DefaultError.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<section className="be-default-error">
<ErrorMask
errorHeader={<FormattedMessage {...messages.defaultErrorMaskHeaderMessage} />}
errorSubHeader={<FormattedMessage {...messages.defaultErrorMaskSubHeaderMessage} />}
/>
</section>
);

export default DefaultError;
87 changes: 87 additions & 0 deletions src/elements/common/error-boundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -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<ErrorComponentProps>;
errorOrigin: ElementOrigin;
onError: (error: ElementsError) => void;
}

type State = {
error?: Error;
};

class ErrorBoundary extends React.Component<ErrorBoundaryProps, State> {
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<string, unknown> = {},
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 <ErrorComponent error={error} />;
}

return React.cloneElement(children, {
...rest,
onError: this.handleError,
});
}
}

export default ErrorBoundary;
2 changes: 2 additions & 0 deletions src/elements/common/error-boundary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './ErrorBoundary';
export { default as withErrorBoundary } from './withErrorBoundary';
22 changes: 22 additions & 0 deletions src/elements/common/error-boundary/withErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -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<P, T = unknown> = React.ComponentType<P & React.RefAttributes<T>>;

const withErrorBoundary =
(errorOrigin: ElementOrigin, errorComponent: React.ComponentType<ErrorComponentProps> = DefaultError) =>
<P extends {}, T = unknown>(WrappedComponent: ComponentWithRef<P, T>) => {
return React.forwardRef<T, P>((props: P, ref: React.Ref<T>) => (
<ErrorBoundary
errorComponent={errorComponent}
errorOrigin={errorOrigin}
{...(props as Record<string, unknown>)}
>
<WrappedComponent {...props} ref={ref} />
</ErrorBoundary>
));
};

export default withErrorBoundary;