@suspensive/react
Version:
Suspensive interfaces for react
156 lines (153 loc) • 6.88 kB
JavaScript
'use client';
import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.130.0/helpers/objectSpread2.mjs";
import { Message_useErrorBoundaryFallbackProps_this_hook_should_be_called_in_ErrorBoundary_props_fallback, Message_useErrorBoundary_this_hook_should_be_called_in_ErrorBoundary_props_children, SuspensiveError } from "./models/SuspensiveError.mjs";
import { ErrorBoundaryGroupContext } from "./ErrorBoundaryGroup.mjs";
import { hasResetKeysChanged } from "./utils/hasResetKeysChanged.mjs";
import { Component, createContext, forwardRef, useContext, useImperativeHandle, useMemo, useRef, useState } from "react";
import { Fragment, jsx } from "react/jsx-runtime";
//#region src/ErrorBoundary.tsx
const matchError = (errorMatcher, error) => {
if (typeof errorMatcher === "boolean") return errorMatcher;
if (typeof errorMatcher === "function") {
try {
if (errorMatcher === Error || errorMatcher.prototype instanceof Error) return error instanceof errorMatcher;
} catch (_unused) {}
try {
if (error instanceof errorMatcher) return true;
} catch (_unused2) {}
try {
return errorMatcher(error);
} catch (_unused3) {
return false;
}
}
return false;
};
const shouldCatchError = (shouldCatch, error) => Array.isArray(shouldCatch) ? shouldCatch.some((errorMatcher) => matchError(errorMatcher, error)) : matchError(shouldCatch, error);
const initialErrorBoundaryState = {
isError: false,
error: null
};
var BaseErrorBoundary = class extends Component {
constructor(..._args) {
super(..._args);
this.state = initialErrorBoundaryState;
this.reset = () => {
var _this$props$onReset, _this$props;
(_this$props$onReset = (_this$props = this.props).onReset) === null || _this$props$onReset === void 0 || _this$props$onReset.call(_this$props);
this.setState(initialErrorBoundaryState);
};
}
static getDerivedStateFromError(error) {
return {
isError: true,
error
};
}
componentDidUpdate(prevProps, prevState) {
const { isError } = this.state;
const { resetKeys } = this.props;
if (isError && prevState.isError && hasResetKeysChanged(prevProps.resetKeys, resetKeys)) this.reset();
}
componentDidCatch(error, info) {
var _this$props$onError, _this$props2;
(_this$props$onError = (_this$props2 = this.props).onError) === null || _this$props$onError === void 0 || _this$props$onError.call(_this$props2, error, info);
}
render() {
const { children, fallback, shouldCatch = true } = this.props;
const { isError, error } = this.state;
let childrenOrFallback = children;
if (isError) {
if (error instanceof SuspensiveError) throw error;
if (error instanceof ErrorInFallback) throw error.originalError;
if (!shouldCatchError(shouldCatch, error)) throw error;
if (typeof fallback === "undefined") {
if (process.env.NODE_ENV === "development") console.error("ErrorBoundary of @suspensive/react requires a defined fallback");
throw error;
}
const Fallback = fallback;
childrenOrFallback = /* @__PURE__ */ jsx(FallbackBoundary, { children: typeof Fallback === "function" ? /* @__PURE__ */ jsx(Fallback, {
error,
reset: this.reset
}) : Fallback });
}
return /* @__PURE__ */ jsx(ErrorBoundaryContext.Provider, {
value: _objectSpread2(_objectSpread2({}, this.state), {}, { reset: this.reset }),
children: childrenOrFallback
});
}
};
var ErrorInFallback = class extends Error {
constructor(originalError) {
super();
this.originalError = originalError;
}
};
var FallbackBoundary = class extends Component {
componentDidCatch(originalError) {
throw originalError instanceof SuspensiveError ? originalError : new ErrorInFallback(originalError);
}
render() {
return this.props.children;
}
};
/**
* This component provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.
* @see {@link https://suspensive.org/docs/react/ErrorBoundary Suspensive Docs}
*/
const ErrorBoundary = Object.assign(forwardRef(function ErrorBoundary(props, ref) {
var _useContext;
const { fallback, children, onError, onReset, resetKeys, shouldCatch } = props;
const group = (_useContext = useContext(ErrorBoundaryGroupContext)) !== null && _useContext !== void 0 ? _useContext : { resetKey: 0 };
const baseErrorBoundaryRef = useRef(null);
useImperativeHandle(ref, () => ({ reset: () => {
var _baseErrorBoundaryRef;
return (_baseErrorBoundaryRef = baseErrorBoundaryRef.current) === null || _baseErrorBoundaryRef === void 0 ? void 0 : _baseErrorBoundaryRef.reset();
} }));
return /* @__PURE__ */ jsx(BaseErrorBoundary, {
shouldCatch,
fallback,
onError,
onReset,
resetKeys: [group.resetKey, ...resetKeys || []],
ref: baseErrorBoundaryRef,
children
});
}), {
displayName: "ErrorBoundary",
with: (errorBoundaryProps, Component) => Object.assign((props) => /* @__PURE__ */ jsx(ErrorBoundary, _objectSpread2(_objectSpread2({}, errorBoundaryProps), {}, { children: /* @__PURE__ */ jsx(Component, _objectSpread2({}, props)) })), { displayName: `${ErrorBoundary.displayName}.with(${Component.displayName || Component.name || "Component"})` }),
Consumer: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children: children(useErrorBoundary()) })
});
const ErrorBoundaryContext = Object.assign(createContext(null), { displayName: "ErrorBoundaryContext" });
/**
* This hook provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.
* @see {@link https://suspensive.org/docs/react/ErrorBoundary#useerrorboundary Suspensive Docs}
*/
const useErrorBoundary = () => {
const [state, setState] = useState({
isError: false,
error: null
});
if (state.isError) throw state.error;
const errorBoundary = useContext(ErrorBoundaryContext);
SuspensiveError.assert(errorBoundary != null && !errorBoundary.isError, Message_useErrorBoundary_this_hook_should_be_called_in_ErrorBoundary_props_children);
return useMemo(() => ({ setError: (error) => setState({
isError: true,
error
}) }), []);
};
/**
* This hook allows you to access the reset method and error objects without prop drilling.
* @see {@link https://suspensive.org/docs/react/ErrorBoundary#useerrorboundaryfallbackprops Suspensive Docs}
*/
const useErrorBoundaryFallbackProps = () => {
const errorBoundary = useContext(ErrorBoundaryContext);
SuspensiveError.assert(errorBoundary != null && errorBoundary.isError, Message_useErrorBoundaryFallbackProps_this_hook_should_be_called_in_ErrorBoundary_props_fallback);
return useMemo(() => ({
error: errorBoundary.error,
reset: errorBoundary.reset
}), [errorBoundary.error, errorBoundary.reset]);
};
//#endregion
export { ErrorBoundary, useErrorBoundary, useErrorBoundaryFallbackProps };
//# sourceMappingURL=ErrorBoundary.mjs.map