/* eslint-disable @typescript-eslint/no-explicit-any */ import { Button, Code, Collapse, Group, Paper, rem, Text } from '@mantine/core'; import { IconExclamationCircle } from '@tabler/icons-react'; import React, { type PropsWithChildren } from 'react'; import errorManager from './ErrorManager'; let ErrorBoundaryOptions = { disabled: false, onError: undefined, } as { disabled?: boolean; onError?: (error: any, errorInfo: any) => void; }; export const SetErrorBoundaryOptions = (options: typeof ErrorBoundaryOptions) => { ErrorBoundaryOptions = { ...ErrorBoundaryOptions, ...options }; }; export const GetErrorBoundaryOptions = () => { return { ...ErrorBoundaryOptions }; }; interface ErrorBoundaryProps extends PropsWithChildren { namespace?: string; onReportClick?: () => void; onResetClick?: () => void; onRetryClick?: () => void; reportAPI?: string; } interface ErrorBoundaryState { error: any; errorInfo: any; reported?: boolean; showDetail: boolean; timer?: NodeJS.Timeout | undefined; try: boolean; tryCnt: number; wasReset?: boolean; } export class ReactErrorBoundary extends React.Component { constructor(props: ErrorBoundaryProps) { super(props); this.state = { error: null, errorInfo: null, showDetail: false, timer: undefined, try: false, tryCnt: 0, }; } componentDidCatch(error: any, errorInfo: any) { if (GetErrorBoundaryOptions()?.disabled) { throw Error('ErrorBoundary pass through enabled, rethrowing error', { cause: error, //@ts-ignore info: errorInfo, }); } // Catch errors in any components below and re-render with error message this.setState({ error, errorInfo, try: false, }); if (typeof GetErrorBoundaryOptions()?.onError === 'function') { GetErrorBoundaryOptions()?.onError?.(error, errorInfo); } // Report error to error manager (Sentry, custom API, etc.) errorManager.reportError(error, errorInfo, { componentStack: errorInfo?.componentStack, namespace: this.props.namespace, }); } render() { if (this.state.errorInfo) { // Error path return (

A react error has occurred!

You can try one of these solutions to get back on track:

{this.state.error && ( {this.props.namespace && In: {this.props.namespace}} {this.state.error.toString()} )} {this.state.errorInfo.componentStack}
); } //Retry code, if you do it too many times, it will stop if (this.state.try) { if (!this.state.timer) { const tm = setTimeout(() => { clearTimeout(this.state.timer); this.setState((state) => ({ ...state, timer: undefined, try: false, })); }, 1000); this.setState((state) => ({ ...state, timer: tm, })); } return
Retrying {this.state.tryCnt}...
; } // Normally, just render children return this.props.children; } async report() { if (this.props?.onReportClick) { this.props.onReportClick(); this.setState(() => ({ reported: true, })); return; } // Manually report error if user clicks report button if (this.state.error && this.state.errorInfo) { await errorManager.reportError(this.state.error, this.state.errorInfo, { componentStack: this.state.errorInfo?.componentStack, namespace: this.props.namespace, tags: { reportedBy: 'user' }, }); this.setState(() => ({ reported: true, })); } } reset() { if (this.props?.onResetClick) { this.props.onResetClick(); this.setState(() => ({ wasReset: true, })); return; } this.setState(() => ({ wasReset: true, })); window.location.reload(); } retry() { this.setState({ try: true, tryCnt: this.state.tryCnt + 1, }); if (this.props?.onRetryClick) { this.props.onRetryClick(); return; } this.setState({ error: undefined, errorInfo: undefined, }); this.forceUpdate(); } } export default ReactErrorBoundary;