Error boundary component that catches React errors and displays error UI. Logs errors for debugging and attempts to dispatch to debug context.

export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}

/**
* Updates state when an error is caught to trigger fallback UI.
* @param error - The thrown error.
* @returns Partial state to set hasError flag.
* @source
*/
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
// Trigger fallback UI on next render
return {
hasError: true,
error,
};
}

/**
* Logs error details and stores error info when an error is caught.
* @param error - The thrown error.
* @param errorInfo - React error info with component stack.
* @source
*/
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
// Log error to console
console.error("🚨 [ErrorBoundary] Caught error:", error);
console.error("🚨 [ErrorBoundary] Error info:", errorInfo);

// Update state with error info
this.setState({
error,
errorInfo,
});

// Capture to Sentry with React context
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
},
},
fingerprint: ["{{ default }}", error.message],
});

// Call onError callback if provided
if (this.props.onError) {
this.props.onError(error, errorInfo);
}

// Dispatch error to debug context if available
try {
const debugEvent = new CustomEvent("debug:log", {
detail: {
type: "app.error",
message: `React Error: ${error.message}`,
level: "error",
metadata: {
stack: error.stack,
componentStack: errorInfo.componentStack,
},
},
});
globalThis.dispatchEvent(debugEvent);
} catch (e) {
console.error("Failed to log error to debug context:", e);
}
}

/**
* Resets the error boundary state to render children again.
* @source
*/
handleReset = (): void => {
// Call custom recovery action if provided
if (this.props.recoveryAction) {
this.props.recoveryAction();
}

this.setState({
hasError: false,
error: null,
errorInfo: null,
});
};

/**
* Reloads the entire application window.
* @source
*/
handleReload = (): void => {
globalThis.location.reload();
};

/**
* Navigates to the home page.
* @source
*/
handleGoHome = (): void => {
globalThis.location.href = "/";
};

/**
* Renders the error boundary UI or children.
* Displays custom fallback if provided, otherwise shows default error UI with recovery options.
* @returns Error UI or children based on error state.
* @source
*/
render(): ReactNode {
if (this.state.hasError) {
// Use custom fallback if provided
if (this.props.fallback) {
return this.props.fallback;
}

// Default error UI
return (
<div className="bg-background flex min-h-screen items-center justify-center p-4">
<Card className="border-destructive w-full max-w-2xl">
<CardHeader>
<div className="flex items-center gap-3">
<div className="bg-destructive/10 rounded-full p-2">
<AlertTriangle className="text-destructive h-6 w-6" />
</div>
<div>
<CardTitle className="text-2xl">
Something went wrong
</CardTitle>
<CardDescription>
The application encountered an unexpected error
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-muted rounded-lg p-4">
<h4 className="mb-2 text-sm font-semibold">Error Details:</h4>
<p className="text-destructive font-mono text-sm">
{this.state.error?.message || "Unknown error"}
</p>
</div>

{this.state.error?.stack && (
<details className="cursor-pointer">
<summary className="text-sm font-semibold hover:underline">
Stack Trace
</summary>
<pre className="bg-muted mt-2 max-h-64 overflow-auto rounded-lg p-4 font-mono text-xs">
{this.state.error.stack}
</pre>
</details>
)}

{this.state.errorInfo?.componentStack && (
<details className="cursor-pointer">
<summary className="text-sm font-semibold hover:underline">
Component Stack
</summary>
<pre className="bg-muted mt-2 max-h-64 overflow-auto rounded-lg p-4 font-mono text-xs">
{this.state.errorInfo.componentStack}
</pre>
</details>
)}

<div className="border-border bg-card rounded-lg border p-4">
<p className="text-muted-foreground text-sm">
<strong>What you can do:</strong>
</p>
<ul className="text-muted-foreground mt-2 list-inside list-disc space-y-1 text-sm">
<li>Try reloading the page</li>
<li>Check the developer console for more details</li>
<li>Report this issue if it persists</li>
</ul>
</div>
</CardContent>
<CardFooter className="flex flex-wrap gap-2">
<Button onClick={this.handleReset} variant="default" size="sm">
<RotateCcw className="mr-2 h-4 w-4" />
Try Again
</Button>
<Button onClick={this.handleReload} variant="outline" size="sm">
<RotateCcw className="mr-2 h-4 w-4" />
Reload App
</Button>
<Button onClick={this.handleGoHome} variant="ghost" size="sm">
<Home className="mr-2 h-4 w-4" />
Go Home
</Button>
</CardFooter>
</Card>
</div>
);
}

return this.props.children;
}
}

Hierarchy

Constructors

Methods

  • Updates state when an error is caught to trigger fallback UI.

    Parameters

    • error: Error

      The thrown error.

    Returns Partial<ErrorBoundaryState>

    Partial state to set hasError flag.

      static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
    // Trigger fallback UI on next render
    return {
    hasError: true,
    error,
    };
    }
  • Logs error details and stores error info when an error is caught.

    Parameters

    • error: Error

      The thrown error.

    • errorInfo: ErrorInfo

      React error info with component stack.

    Returns void

      componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    // Log error to console
    console.error("🚨 [ErrorBoundary] Caught error:", error);
    console.error("🚨 [ErrorBoundary] Error info:", errorInfo);

    // Update state with error info
    this.setState({
    error,
    errorInfo,
    });

    // Capture to Sentry with React context
    Sentry.captureException(error, {
    contexts: {
    react: {
    componentStack: errorInfo.componentStack,
    },
    },
    fingerprint: ["{{ default }}", error.message],
    });

    // Call onError callback if provided
    if (this.props.onError) {
    this.props.onError(error, errorInfo);
    }

    // Dispatch error to debug context if available
    try {
    const debugEvent = new CustomEvent("debug:log", {
    detail: {
    type: "app.error",
    message: `React Error: ${error.message}`,
    level: "error",
    metadata: {
    stack: error.stack,
    componentStack: errorInfo.componentStack,
    },
    },
    });
    globalThis.dispatchEvent(debugEvent);
    } catch (e) {
    console.error("Failed to log error to debug context:", e);
    }
    }
  • Renders the error boundary UI or children. Displays custom fallback if provided, otherwise shows default error UI with recovery options.

    Returns ReactNode

    Error UI or children based on error state.

      render(): ReactNode {
    if (this.state.hasError) {
    // Use custom fallback if provided
    if (this.props.fallback) {
    return this.props.fallback;
    }

    // Default error UI
    return (
    <div className="bg-background flex min-h-screen items-center justify-center p-4">
    <Card className="border-destructive w-full max-w-2xl">
    <CardHeader>
    <div className="flex items-center gap-3">
    <div className="bg-destructive/10 rounded-full p-2">
    <AlertTriangle className="text-destructive h-6 w-6" />
    </div>
    <div>
    <CardTitle className="text-2xl">
    Something went wrong
    </CardTitle>
    <CardDescription>
    The application encountered an unexpected error
    </CardDescription>
    </div>
    </div>
    </CardHeader>
    <CardContent className="space-y-4">
    <div className="bg-muted rounded-lg p-4">
    <h4 className="mb-2 text-sm font-semibold">Error Details:</h4>
    <p className="text-destructive font-mono text-sm">
    {this.state.error?.message || "Unknown error"}
    </p>
    </div>

    {this.state.error?.stack && (
    <details className="cursor-pointer">
    <summary className="text-sm font-semibold hover:underline">
    Stack Trace
    </summary>
    <pre className="bg-muted mt-2 max-h-64 overflow-auto rounded-lg p-4 font-mono text-xs">
    {this.state.error.stack}
    </pre>
    </details>
    )}

    {this.state.errorInfo?.componentStack && (
    <details className="cursor-pointer">
    <summary className="text-sm font-semibold hover:underline">
    Component Stack
    </summary>
    <pre className="bg-muted mt-2 max-h-64 overflow-auto rounded-lg p-4 font-mono text-xs">
    {this.state.errorInfo.componentStack}
    </pre>
    </details>
    )}

    <div className="border-border bg-card rounded-lg border p-4">
    <p className="text-muted-foreground text-sm">
    <strong>What you can do:</strong>
    </p>
    <ul className="text-muted-foreground mt-2 list-inside list-disc space-y-1 text-sm">
    <li>Try reloading the page</li>
    <li>Check the developer console for more details</li>
    <li>Report this issue if it persists</li>
    </ul>
    </div>
    </CardContent>
    <CardFooter className="flex flex-wrap gap-2">
    <Button onClick={this.handleReset} variant="default" size="sm">
    <RotateCcw className="mr-2 h-4 w-4" />
    Try Again
    </Button>
    <Button onClick={this.handleReload} variant="outline" size="sm">
    <RotateCcw className="mr-2 h-4 w-4" />
    Reload App
    </Button>
    <Button onClick={this.handleGoHome} variant="ghost" size="sm">
    <Home className="mr-2 h-4 w-4" />
    Go Home
    </Button>
    </CardFooter>
    </Card>
    </div>
    );
    }

    return this.props.children;
    }