Files
silo-server/web/src/components/ErrorBoundary.tsx
T

95 lines
2.9 KiB
TypeScript

import { Component, type ErrorInfo, type ReactNode } from "react";
import { useLocation } from "react-router";
interface ErrorBoundaryProps {
children: ReactNode;
resetKeys?: unknown[];
}
interface ErrorBoundaryState {
hasError: boolean;
prevResetKeys: unknown[];
}
class ErrorBoundaryInner extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, prevResetKeys: props.resetKeys ?? [] };
}
static getDerivedStateFromError(): Partial<ErrorBoundaryState> {
return { hasError: true };
}
static getDerivedStateFromProps(
props: ErrorBoundaryProps,
state: ErrorBoundaryState,
): Partial<ErrorBoundaryState> | null {
const nextKeys = props.resetKeys ?? [];
if (
state.hasError &&
nextKeys.length === state.prevResetKeys.length &&
nextKeys.every((key, i) => key === state.prevResetKeys[i])
) {
return null;
}
if (state.hasError) {
return { hasError: false, prevResetKeys: nextKeys };
}
if (
nextKeys.length !== state.prevResetKeys.length ||
nextKeys.some((key, i) => key !== state.prevResetKeys[i])
) {
return { prevResetKeys: nextKeys };
}
return null;
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("[ErrorBoundary]", error, info.componentStack);
}
render() {
if (this.state.hasError) {
return (
<div className="bg-background flex min-h-screen items-center justify-center p-8">
<div className="max-w-md text-center">
<h1 className="text-foreground mb-4 text-2xl font-bold">Something went wrong</h1>
<p className="text-muted-foreground mb-6">
An unexpected error occurred. Try refreshing the page.
</p>
<div className="flex items-center justify-center gap-3">
<button
type="button"
onClick={() => this.setState({ hasError: false })}
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2"
>
Try again
</button>
<button
type="button"
onClick={() => window.location.reload()}
className="border-border hover:bg-muted/40 rounded-md border px-4 py-2"
>
Refresh Page
</button>
</div>
</div>
</div>
);
}
return this.props.children;
}
}
/** Wrapper that passes location.pathname as a resetKey so errors clear on navigation. */
export function ErrorBoundary({ children, resetKeys = [], ...rest }: ErrorBoundaryProps) {
const location = useLocation();
return (
<ErrorBoundaryInner resetKeys={[location.pathname, ...resetKeys]} {...rest}>
{children}
</ErrorBoundaryInner>
);
}