From 80a5db3e912793ccb65489e34372c5cd7bb0f697 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Thu, 24 Apr 2025 16:45:46 -0700 Subject: [PATCH] Error resiliency with custom Error boundary --- src/common/components/ErrorBoundary.tsx | 130 ++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/common/components/ErrorBoundary.tsx diff --git a/src/common/components/ErrorBoundary.tsx b/src/common/components/ErrorBoundary.tsx new file mode 100644 index 000000000..2dc8ac696 --- /dev/null +++ b/src/common/components/ErrorBoundary.tsx @@ -0,0 +1,130 @@ +import * as React from 'react'; + +import { logger } from '~/common/logger'; + + +export interface ErrorBoundaryProps { + /** UNUSED: just marks the fact that this boundary is the outer */ + outer?: boolean; + /** Optional: A simple React node to display when an error is caught. */ + fallback?: React.ReactNode; + /** Optional: A name for this boundary, useful for logging context */ + componentName?: string; + /** Optional: Callback function when an error is caught (e.g., for external reporting like Sentry) */ + onError?: (error: Error, errorInfo: React.ErrorInfo) => void; + /** Optional: Called when the reset button in the default fallback is clicked */ + onReset?: () => void; // Added for flexibility with default fallback + /** Content to render when no error occurs */ + children: React.ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + + +/** + * A reusable React Error Boundary component using Sherpa styles for fallback. + * Catches JavaScript errors anywhere in its child component tree, + * logs those errors using the provided logger, and displays a fallback UI. + */ +export class ErrorBoundary extends React.Component { + + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static defaultProps: Partial = { + componentName: 'UnnamedBoundary', + }; + + static getDerivedStateFromError(error: Error): Partial { + // Update state so the next render will show the fallback UI. + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + const { componentName, onError } = this.props; + + // Log the error using the custom logger + logger.error( + `ErrorBoundary caught an error in ${componentName}`, + { + error: { name: error.name, message: error.message, stack: error.stack }, + componentStack: errorInfo.componentStack, + }, + ); + + // Call the optional onError callback for external reporting + onError?.(error, errorInfo); + } + + resetErrorBoundary = (): void => { + const { onReset } = this.props; + onReset?.(); + this.setState({ hasError: false, error: null }); + }; + + render(): React.ReactNode { + const { hasError, error } = this.state; + const { outer, children, fallback } = this.props; + + if (hasError && error) + return fallback ? fallback : ( +
+
+
+

Oops, we hit a snag

+
+

Something broke; this shouldn't happen.{outer ? ' Please try reloading Big-AGI.' : ''}

+ {/* Dev-only stack trace */} + {/*{!Release.IsNodeDevBuild ? (*/} + {/*
*/} + {/* {error?.message}*/} + {/*
*/} + {/*) : (*/} +
+ Error Details (Dev) +
{`---\n${error?.toString()}\n---\nStack:\n${error?.stack}`}
+
+ {/*)}*/} +
+
+
+ {outer ? ( + + ) : ( + + )} +
+
+
+ ); + + return children; + } +}