import React, { Component, Children, ReactNode, ErrorInfo, ComponentType, } from 'react'; import * as Sentry from '@sentry/react'; import { getComponentDisplayName } from 'utils'; import { __ } from '@wordpress/i18n'; import { createInterpolateElement } from '@wordpress/element'; import { ExternalLink } from '@wordpress/components'; interface ErrorBoundaryState { hasError: boolean; error?: string | null; componentStack?: ErrorInfo | null; eventId?: string; } export interface ErrorBoundaryProps { onError?: ( state: ErrorBoundaryState ) => ReactNode; children?: ReactNode; } export class ErrorBoundary extends Component< ErrorBoundaryProps, ErrorBoundaryState > { constructor( props: ErrorBoundaryProps ) { super( props ); this.state = { hasError: false, error: null, componentStack: null, eventId: '', }; } static getDerivedStateFromError( error: Error & { fileName?: string; } ) { return { hasError: true, error: `${ error?.toString() } \nFile name: ${ error?.fileName ?? 'No fileName reported' } \nStack trace: ${ error?.stack ?? 'No stack trace reported' }`, }; } componentDidCatch( error: Error, componentStack: ErrorInfo ) { // eslint-disable-next-line no-console console.warn( 'logging', { error, componentStack } ); // Log captured error with info to Sentry. Sentry.withScope( () => { const eventId = Sentry.captureException( error, { captureContext: { contexts: { react: { componentStack } }, }, } ); this.setState( { eventId, componentStack } ); } ); } render() { const { onError, children } = this.props; const { hasError, error } = this.state; if ( hasError ) { if ( onError ) { return onError( this.state ); } return ( <>
{ createInterpolateElement(
__(
'Please report the following error by
{ error }
>
);
}
return this.props.children;
}
}