Skip to content

Commit

Permalink
feat(core): add ErrorBoundary component
Browse files Browse the repository at this point in the history
catch JavaScript errors anywhere in their child component tree, log those errors, and display a
fallback UI instead of the component tree that crashed

improves #33
  • Loading branch information
aneurysmjs committed Sep 3, 2019
1 parent 1716fe6 commit c6146f6
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
87 changes: 87 additions & 0 deletions src/app/components/core/ErrorBoundary/ErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// @flow strict
import React, { Component } from 'react';
import type { ComponentType } from 'react';

import ErrorBoundaryFallbackComponent from './ErrorBoundaryFallbackComponent';

type Props = {
children?: *,
FallbackComponent: ComponentType<*>,
onError?: (error: Error, componentStack: string) => void,
};

type ErrorInfo = {
componentStack: string,
};

type State = {
error: ?Error,
info: ?ErrorInfo,
};

class ErrorBoundary extends Component<Props, State> {
static defaultProps = {
FallbackComponent: ErrorBoundaryFallbackComponent,
};

state = {
error: null,
info: null,
};

componentDidCatch(error: Error, info: ErrorInfo): void {
const { onError } = this.props;

if (typeof onError === 'function') {
try {
onError.call(this, error, info ? info.componentStack : '');
// eslint-disable-next-line no-empty
} catch (ignoredError) {}
}

this.setState({ error, info });
}

render() {
const { children, FallbackComponent } = this.props;
const { error, info } = this.state;

if (error != null) {
return (
<FallbackComponent
componentStack={
info ? info.componentStack : ''
}
error={error}
/>
);
}

return children || null;
}
}

export const withErrorBoundary = (
SomeComponent: ComponentType<*>,
FallbackComponent: ComponentType<*>,
onError: (Error) => void,
): ComponentType<{}> => {
const Wrapped = (props) => (
<ErrorBoundary
FallbackComponent={FallbackComponent} onError={onError}
>
<SomeComponent {...props} />
</ErrorBoundary>
);

// Format for display in DevTools
const name = SomeComponent.displayName || SomeComponent.name;

Wrapped.displayName = name
? `WithErrorBoundary(${name})`
: 'WithErrorBoundary';

return Wrapped;
};

export default ErrorBoundary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @flow strict

import React from 'react';

type Props = {
componentStack: string,
error: Error,
};

const toTitle = (
error: Error,
componentStack: string,
): string => (`${error.toString()}\n\nThis is located at:${componentStack}`);

const style = {
alignItems: 'center',
boxSizing: 'border-box',
color: '#FFF',
cursor: 'help',
display: 'flex',
flexDirection: 'column',
height: '100%',
maxHeight: '100vh',
maxWidth: '100vw',
textAlign: 'center',
width: '100%',
};

const ErrorBoundaryFallbackComponent = ({ componentStack, error }: Props) => (
<div
style={style}
title={toTitle(error, componentStack)}
>
<pre>{componentStack}</pre>
</div>
);

export default ErrorBoundaryFallbackComponent;
7 changes: 7 additions & 0 deletions src/app/components/core/ErrorBoundary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @flow strict
import ErrorBoundaryFallbackComponent from './ErrorBoundaryFallbackComponent';
import ErrorBoundary, { withErrorBoundary } from './ErrorBoundary';

export default ErrorBoundary;

export { ErrorBoundary, withErrorBoundary, ErrorBoundaryFallbackComponent };

0 comments on commit c6146f6

Please sign in to comment.