Signal-Desktop/ts/components/fun/base/FunErrorBoundary.tsx

67 lines
1.8 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode, ErrorInfo } from 'react';
import React, { Component, useCallback } from 'react';
import { createLogger } from '../../../logging/log';
import * as Errors from '../../../types/errors';
import { ToastType } from '../../../types/Toast';
const log = createLogger('FunErrorBoundary');
type ErrorBoundaryProps = Readonly<{
onError: (error: unknown, info: ErrorInfo) => void;
fallback: (error: unknown) => ReactNode;
children: ReactNode;
}>;
type ErrorBoundaryState = {
caught?: { error: unknown };
};
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
// eslint-disable-next-line react/state-in-constructor
override state: ErrorBoundaryState = {};
static getDerivedStateFromError(error: unknown) {
return { caught: { error } };
}
override componentDidCatch(error: unknown, info: ErrorInfo) {
this.props.onError(error, info);
}
override render() {
if (this.state.caught != null) {
return this.props.fallback(this.state.caught.error);
}
return this.props.children;
}
}
export type FunErrorBoundaryProps = Readonly<{
children: ReactNode;
}>;
export function FunErrorBoundary(props: FunErrorBoundaryProps): JSX.Element {
const fallback = useCallback(() => {
return <div className="FunErrorBoundary" />;
}, []);
const handleError = useCallback((error: unknown, info: ErrorInfo) => {
log.error(
'ErrorBoundary: Caught error',
Errors.toLogFormat(error),
info.componentStack
);
window.reduxActions?.toast.showToast({ toastType: ToastType.Error });
}, []);
return (
<ErrorBoundary fallback={fallback} onError={handleError}>
{props.children}
</ErrorBoundary>
);
}