Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 33x 33x 33x 1x 1x 1x 16x 16x 16x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 61x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 37x 37x 1x 1x | import { captureOwnerStack, Component, ReactNode } from 'react';
import { trackRenderError } from '@/helpers/analytics';
import { getErrorMessage } from '@/helpers/exceptions';
import { i18n } from '@/helpers/i18n';
import { showReportDialog } from './ReportDialog';
import { ErrorReportButton } from './StyledComponents';
import { Logger } from '@/utils/Logger';
const logger = new Logger('ErrorBoundary');
/** Internal state: whether a descendant threw, and the captured error details. */
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
componentStack?: string;
}
/**
* ErrorBoundary component that catches JavaScript errors anywhere in their child component tree,
* logs those errors, and displays a fallback UI instead of the component tree that crashed.
* The fallback includes a "Report Error" button that opens the bug-report dialog prefilled with
* the caught error and its component stack.
*
* @component
*
* @param props - Component props
*
* @example
* ```tsx
* <ErrorBoundary fallback={<p>Something went wrong</p>}>
* <MyComponent />
* </ErrorBoundary>
* ```
* @source
*/
class ErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
ErrorBoundaryState
> {
/**
* Creates an instance of ErrorBoundary.
* @param props - Component props
* @source
*/
constructor(props: { children: ReactNode; fallback: ReactNode }) {
super(props);
this.state = { hasError: false };
}
/**
* Static lifecycle method that is called when a descendant component throws an error.
* Updates the state to show the fallback UI and retain the error on the next render.
* @param error - The error that was thrown
* @returns New state object with hasError set to true and the error captured
* @source
*/
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// Update state so the next render will show the fallback UI.
return { hasError: true, error };
}
/**
* Lifecycle method that is called after an error has been thrown in a descendant component.
* Logs the error and component stack trace, and retains the component stack for reporting.
* @param error - The error that was thrown
* @param info - React-supplied error info, including the component stack
* @source
*/
componentDidCatch(error: Error, info: { componentStack: string }) {
logger.fatal(`Component render error: ${getErrorMessage(error)}`, {
error,
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
componentStack: info.componentStack,
// Warning: `captureOwnerStack` is not available in production.
ownerStack: captureOwnerStack(),
});
this.setState({ componentStack: info.componentStack });
// Report the crash to PostHog, component stack included (non-PII, best-effort).
void trackRenderError(error, {}, info.componentStack);
}
/**
* Opens the bug-report dialog prefilled with the caught error and component stack.
* @source
*/
handleReport = () => {
void showReportDialog(this.state.error, {
action: 'render-crash',
componentStack: this.state.componentStack,
});
};
/**
* Renders either the children or the fallback UI (plus a report button) based on
* whether an error has occurred.
* @returns The rendered component
* @source
*/
render() {
if (this.state.hasError) {
// Rendered above the app's ThemeProvider, so use a plain button that needs no theme rather
// than a themed MUI component (see ErrorReportButton).
return (
<>
{this.props.fallback}
<ErrorReportButton
type="button"
data-testid="error-boundary-report"
onClick={this.handleReport}
>
{i18n('error_boundary_report')}
</ErrorReportButton>
</>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
|