diff --git a/packages/frontend/src/components/App.tsx b/packages/frontend/src/components/App.tsx index a277b98..dd36b8b 100644 --- a/packages/frontend/src/components/App.tsx +++ b/packages/frontend/src/components/App.tsx @@ -6,6 +6,7 @@ import { hot } from 'react-hot-loader'; import { Redirect, Route, Switch } from 'react-router'; import { Router } from 'react-router-dom'; import history from '../util/history'; +import { ErrorBoundary } from './general/ErrorBoundary'; import { AppPath, APP_ROUTES } from '../util/routes'; // Global bootstrap: install subsystems and load configuration @@ -16,7 +17,9 @@ export const AppComponent = () => ( {APP_ROUTES.map((routeProps, index) => ( - + + + ))} {/* Error 404 Fallback */} diff --git a/packages/frontend/src/components/general/ErrorBoundary.tsx b/packages/frontend/src/components/general/ErrorBoundary.tsx new file mode 100644 index 0000000..eee30d0 --- /dev/null +++ b/packages/frontend/src/components/general/ErrorBoundary.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; + +/** + * Properties of Error Boundary + */ +export interface ErrorBoundaryProps { + /** + * Callback used to render ui for the error once it occured + */ + readonly errorRenderer?: (error: Error) => React.ReactNode; +} + +interface State { + readonly error?: Error; +} + +function renderError(error: Error): React.ReactNode { + return error.message; +} + +/** + * ErrorBoundary component catches all errors that occur somewhere bellow in the DOM + * tree and displays an error message instead of taking the entire page down. + * + * Use by wrapping it around failable components in JSX + * or use the withErrorBoundary(...) to wrap component components + */ +export class ErrorBoundary extends React.Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = {}; + } + + static getDerivedStateFromError(error: Error) { + return { error }; + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + console.error('Caught error:', error, 'with info:', info); + console.trace(); + } + + render() { + const { error } = this.state; + if (error) { + return renderError(error); + } + return <>{this.props.children}; + } +} + +/** + * ErrorBoundary component catches all errors that occur somewhere bellow in the DOM + * tree and displays an error message instead of taking the entire page down. + * + * Use by wrapping it around failable components in JSX, i.e., ... + * or using this component wrapper + * + * @param WrappedComponent component to wrap + * @param errorRenderer callback to render error ui + */ +export function withErrorBoundary( + WrappedComponent: React.ComponentType, + errorRenderer: (error: Error) => React.ReactNode = renderError, +): React.ComponentType { + return (props: TProps) => ( + + + + ); +}