69 lines
1.7 KiB
React
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" />;
|
|
}
|