From 1965b3abd6938a038668ecfc922125681dd7601e Mon Sep 17 00:00:00 2001 From: Stefan Probst Date: Mon, 27 Jan 2025 16:46:30 +0100 Subject: [PATCH] feat: add error boundary component --- components/error-boundary.tsx | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 components/error-boundary.tsx diff --git a/components/error-boundary.tsx b/components/error-boundary.tsx new file mode 100644 index 0000000..0041781 --- /dev/null +++ b/components/error-boundary.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { assert } from "@acdh-oeaw/lib"; +import { Component, createContext, type ErrorInfo, type ReactNode, useContext } from "react"; + +interface ErrorBoundaryContextValue { + error: Error; + reset: () => void; +} + +const ErrorBoundaryContext = createContext(null); + +export function useErrorBoundary() { + const value = useContext(ErrorBoundaryContext); + + assert(value != null, "`useErrorBoundary` must be used within an `ErrorBoundary` component."); + + return value; +} + +interface ErrorBoundaryProps { + children: ReactNode; + fallback: ReactNode; + onError?: (error: Error, errorInfo: ErrorInfo) => void; +} + +type ErrorBoundaryState = + | { + status: "default"; + error: null; + } + | { + status: "error"; + error: Error; + }; + +const initialState: ErrorBoundaryState = { + status: "default", + error: null, +}; + +export class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props); + + this.state = initialState; + + this.reset = this.reset.bind(this); + } + + static getDerivedStateFromError(error: unknown) { + return { + status: "error", + error: error instanceof Error ? error : new Error(`Error: ${String(error)}`), + }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.props.onError?.(error, errorInfo); + } + + reset() { + this.setState(initialState); + } + + render() { + if (this.state.status === "error") { + return ( + // eslint-disable-next-line @typescript-eslint/unbound-method + + {this.props.fallback} + + ); + } + + return this.props.children; + } +}