import * as React from "react"; import { ErrorState } from "@/components/ui/error-state"; interface ErrorBoundaryState { error: Error | null; } interface ErrorBoundaryProps { children: React.ReactNode; /** Render prop for full custom fallback */ fallback?: (error: Error, reset: () => void) => React.ReactNode; /** Reset boundary when any value in this array changes (like `useEffect` deps) */ resetKeys?: unknown[]; /** Called once when a render error is caught */ onError?: (error: Error, info: React.ErrorInfo) => void; } /** * Catches render-time errors anywhere below it and shows a fallback UI. * Wrap the whole app at the root (catches everything) and optionally * wrap individual routes/sections for finer-grained recovery. * * Note: does NOT catch errors inside event handlers, async code, or * server-side rendering. For those, surface errors via React Query * `onError` or `toast.error()`. */ export class ErrorBoundary extends React.Component< ErrorBoundaryProps, ErrorBoundaryState > { state: ErrorBoundaryState = { error: null }; static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { error }; } componentDidCatch(error: Error, info: React.ErrorInfo) { this.props.onError?.(error, info); // eslint-disable-next-line no-console console.error("[ErrorBoundary]", error, info); } componentDidUpdate(prevProps: ErrorBoundaryProps) { if (!this.state.error) return; const prev = prevProps.resetKeys ?? []; const next = this.props.resetKeys ?? []; if ( prev.length !== next.length || prev.some((v, i) => !Object.is(v, next[i])) ) { this.reset(); } } reset = () => this.setState({ error: null }); render() { const { error } = this.state; if (!error) return this.props.children; if (this.props.fallback) return this.props.fallback(error, this.reset); return (
); } }