Files
clean-architecture-frontend…/src/presentation/boundaries/render-error-boundary.jsx
T

69 lines
1.7 KiB
React

import { Component } from "react";
/**
* @typedef {{
* children: React.ReactNode,
* boundaryName: string,
* routeId: string,
* buildId: string,
* telemetry?: import("../../application/ports/telemetry-port.js").TelemetryPort,
* fallback?: React.ReactNode
* }} RenderBoundaryProps
* @typedef {{ hasError: boolean }} RenderBoundaryState
*/
/** @extends {Component<RenderBoundaryProps, RenderBoundaryState>} */
export class RenderErrorBoundary extends Component {
/** @param {RenderBoundaryProps} props */
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch() {
try {
this.props.telemetry?.emit("ui.render.failed", {
route_id: this.props.routeId,
build_id: this.props.buildId,
component_boundary: this.props.boundaryName,
});
} catch {
// Telemetry must never recurse into another render failure.
}
}
reset = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
this.props.fallback ?? (
<section role="alert">
<p>error.render_failure</p>
<button type="button" onClick={this.reset}>
retry
</button>
</section>
)
);
}
return this.props.children;
}
}
/** @param {Omit<RenderBoundaryProps, "boundaryName">} props */
export function RouteBoundary(props) {
return <RenderErrorBoundary {...props} boundaryName="route" />;
}
/** @param {Omit<RenderBoundaryProps, "boundaryName">} props */
export function FeatureBoundary(props) {
return <RenderErrorBoundary {...props} boundaryName="feature" />;
}