react-error-boundary
Version:
Simple reusable React error boundary component
1 lines • 11.5 kB
Source Map (JSON)
{"version":3,"file":"react-error-boundary.cjs","sources":["../lib/context/ErrorBoundaryContext.ts","../lib/components/ErrorBoundary.tsx","../lib/utils/isErrorBoundaryContext.ts","../lib/utils/assertErrorBoundaryContext.ts","../lib/hooks/useErrorBoundary.ts","../lib/utils/withErrorBoundary.ts"],"sourcesContent":["import { createContext } from \"react\";\n\nexport type ErrorBoundaryContextType = {\n didCatch: boolean;\n error: Error | null;\n resetErrorBoundary: (...args: unknown[]) => void;\n};\n\nexport const ErrorBoundaryContext =\n createContext<ErrorBoundaryContextType | null>(null);\n","import { Component, createElement, type ErrorInfo } from \"react\";\nimport { ErrorBoundaryContext } from \"../context/ErrorBoundaryContext\";\nimport type { ErrorBoundaryProps, FallbackProps } from \"../types\";\n\nconst isDevelopment = import.meta.env.DEV;\n\ntype ErrorBoundaryState =\n | {\n didCatch: true;\n error: Error;\n }\n | {\n didCatch: false;\n error: null;\n };\n\nconst initialState: ErrorBoundaryState = {\n didCatch: false,\n error: null,\n};\n\n/**\n * A reusable React [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) component.\n * Wrap this component around other React components to \"catch\" errors and render a fallback UI.\n *\n * This package is built on top of React [error boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary),\n * so it has all of the advantages and constraints of that API.\n * This means that it can't catch errors during:\n * - Server side rendering</li>\n * - Event handlers\n * - Asynchronous code (including effects)\n *\n * ℹ️ The component provides several ways to render a fallback: `fallback`, `fallbackRender`, and `FallbackComponent`.\n * Refer to the documentation to determine which is best for your application.\n *\n * ℹ️ This is a **client component**. You can only pass props to it that are serializeable or use it in files that have a `\"use client\";` directive.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n\n this.resetErrorBoundary = this.resetErrorBoundary.bind(this);\n this.state = initialState;\n }\n\n static getDerivedStateFromError(error: Error) {\n return { didCatch: true, error };\n }\n\n resetErrorBoundary(...args: unknown[]) {\n const { error } = this.state;\n\n if (error !== null) {\n this.props.onReset?.({\n args,\n reason: \"imperative-api\",\n });\n\n this.setState(initialState);\n }\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n this.props.onError?.(error, info);\n }\n\n componentDidUpdate(\n prevProps: ErrorBoundaryProps,\n prevState: ErrorBoundaryState,\n ) {\n const { didCatch } = this.state;\n const { resetKeys } = this.props;\n\n // There's an edge case where if the thing that triggered the error happens to *also* be in the resetKeys array,\n // we'd end up resetting the error boundary immediately.\n // This would likely trigger a second error to be thrown.\n // So we make sure that we don't check the resetKeys on the first call of cDU after the error is set.\n\n if (\n didCatch &&\n prevState.error !== null &&\n hasArrayChanged(prevProps.resetKeys, resetKeys)\n ) {\n this.props.onReset?.({\n next: resetKeys,\n prev: prevProps.resetKeys,\n reason: \"keys\",\n });\n\n this.setState(initialState);\n }\n }\n\n render() {\n const { children, fallbackRender, FallbackComponent, fallback } =\n this.props;\n const { didCatch, error } = this.state;\n\n let childToRender = children;\n\n if (didCatch) {\n const props: FallbackProps = {\n error,\n resetErrorBoundary: this.resetErrorBoundary,\n };\n\n if (typeof fallbackRender === \"function\") {\n childToRender = fallbackRender(props);\n } else if (FallbackComponent) {\n childToRender = createElement(FallbackComponent, props);\n } else if (fallback !== undefined) {\n childToRender = fallback;\n } else {\n if (isDevelopment) {\n console.error(\n \"react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop\",\n );\n }\n\n throw error;\n }\n }\n\n return createElement(\n ErrorBoundaryContext.Provider,\n {\n value: {\n didCatch,\n error,\n resetErrorBoundary: this.resetErrorBoundary,\n },\n },\n childToRender,\n );\n }\n}\n\nfunction hasArrayChanged(a: unknown[] = [], b: unknown[] = []) {\n return (\n a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]))\n );\n}\n","import type { ErrorBoundaryContextType } from \"../context/ErrorBoundaryContext\";\n\nexport function isErrorBoundaryContext(\n value: unknown,\n): value is ErrorBoundaryContextType {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"didCatch\" in value &&\n typeof value.didCatch === \"boolean\" &&\n \"error\" in value &&\n \"resetErrorBoundary\" in value &&\n typeof value.resetErrorBoundary === \"function\"\n );\n}\n","import type { ErrorBoundaryContextType } from \"../context/ErrorBoundaryContext\";\nimport { isErrorBoundaryContext } from \"./isErrorBoundaryContext\";\n\nexport function assertErrorBoundaryContext(\n value: unknown,\n): asserts value is ErrorBoundaryContextType {\n if (!isErrorBoundaryContext(value)) {\n throw new Error(\"ErrorBoundaryContext not found\");\n }\n}\n","import { useContext, useMemo, useState } from \"react\";\nimport { ErrorBoundaryContext } from \"../context/ErrorBoundaryContext\";\nimport { assertErrorBoundaryContext } from \"../utils/assertErrorBoundaryContext\";\n\ntype UseErrorBoundaryState =\n | { error: Error; hasError: true }\n | { error: null; hasError: false };\n\nexport type UseErrorBoundaryApi = {\n error: Error | null;\n resetBoundary: () => void;\n showBoundary: (error: Error) => void;\n};\n\n/**\n * Convenience hook for imperatively showing or dismissing error boundaries.\n *\n * ⚠️ This hook must only be used within an `ErrorBoundary` subtree.\n */\nexport function useErrorBoundary(): {\n /**\n * The currently visible `Error` (if one has been thrown).\n */\n error: Error | null;\n\n /**\n * Method to reset and retry the nearest active error boundary (if one is active).\n */\n resetBoundary: () => void;\n\n /**\n * Trigger the nearest error boundary to display the error provided.\n *\n * ℹ️ React only handles errors thrown during render or during component lifecycle methods (e.g. effects and did-mount/did-update).\n * Errors thrown in event handlers, or after async code has run, will not be caught.\n * This method is a way to imperatively trigger an error boundary during these phases.\n */\n showBoundary: (error: Error) => void;\n} {\n const context = useContext(ErrorBoundaryContext);\n\n assertErrorBoundaryContext(context);\n\n const { error, resetErrorBoundary } = context;\n\n const [state, setState] = useState<UseErrorBoundaryState>({\n error: null,\n hasError: false,\n });\n\n const memoized = useMemo(\n () => ({\n error,\n resetBoundary: () => {\n resetErrorBoundary();\n setState({ error: null, hasError: false });\n },\n showBoundary: (error: Error) =>\n setState({\n error,\n hasError: true,\n }),\n }),\n [error, resetErrorBoundary],\n );\n\n if (state.hasError) {\n throw state.error;\n }\n\n return memoized;\n}\n","import {\n createElement,\n forwardRef,\n type ComponentClass,\n type ComponentType,\n} from \"react\";\nimport { ErrorBoundary } from \"../components/ErrorBoundary\";\nimport type { ErrorBoundaryProps } from \"../types\";\n\nexport function withErrorBoundary<\n Type extends ComponentClass<unknown>,\n Props extends object,\n>(Component: ComponentType<Props>, errorBoundaryProps: ErrorBoundaryProps) {\n const Wrapped = forwardRef<InstanceType<Type>, Props>((props, ref) =>\n createElement(\n ErrorBoundary,\n errorBoundaryProps,\n createElement(Component, { ...props, ref } as Props),\n ),\n );\n\n // Format for display in DevTools\n const name = Component.displayName || Component.name || \"Unknown\";\n Wrapped.displayName = `withErrorBoundary(${name})`;\n\n return Wrapped;\n}\n"],"names":["ErrorBoundaryContext","createContext","initialState","ErrorBoundary","Component","props","error","args","info","prevProps","prevState","didCatch","resetKeys","hasArrayChanged","children","fallbackRender","FallbackComponent","fallback","childToRender","createElement","a","b","item","index","isErrorBoundaryContext","value","assertErrorBoundaryContext","useErrorBoundary","context","useContext","resetErrorBoundary","state","setState","useState","memoized","useMemo","withErrorBoundary","errorBoundaryProps","Wrapped","forwardRef","ref","name"],"mappings":"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"}