web-error-handling-error-boundaries-8e6f88 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-error-handling-error-boundaries-8e6f88 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: A boundary catches errors thrown during render, in lifecycle methods and in constructors, and swaps the subtree for fallback UI. It never sees event-handler, async or server-render errors — those reach it only when something callsshowBoundary(). Boundaries are class components, becausegetDerivedStateFromErrorandcomponentDidCatchhave no hook equivalent. React 19 addsonCaughtError,onUncaughtErrorandonRecoverableErroroncreateRoot, which log rather than render and are silently ignored on React 18.
Detailed Resources:
react-error-boundary usage, resetKeys, useErrorBoundary, granular placementcreateRoot/hydrateRoot error options, captureOwnerStack(), error filteringgetDerivedStateFromError pluscomponentDidCatch is the whole API, and examples/core.md has it in full.
resetKeys, useErrorBoundary and theFallbackProps type rather than reimplementing them, and follow examples/core.md.
createRoot options reportevery error including the ones no boundary caught; see examples/react-19-hooks.md.
<critical_requirements>
Return new state from `getDerivedStateFromError` and put every side effect in `componentDidCatch`. The first runs during render, where a fetch or a log call breaks React's phase rules; the second runs at commit, where they are safe.
Wrap each feature area in its own boundary as well as the root. A single root boundary turns one failing widget into a blank page, and the fallback can say what failed only when it sits beside the thing that failed.
Give the fallback a way back — a reset callback, `resetKeys`, or both. Without one the only recovery a user has is a full page reload, which costs them everything they had typed.
Put `role="alert"` on the fallback and make its controls real buttons. The subtree vanishing is silent otherwise, and a screen reader user gets no announcement that anything went wrong.
Route async and event-handler failures through `showBoundary()`. A boundary cannot see a rejected promise, so an unhandled one leaves the UI showing stale content with no error state at all.
</critical_requirements>
Auto-detection: error boundary, ErrorBoundary, getDerivedStateFromError, componentDidCatch, fallback UI, react-error-boundary, useErrorBoundary, showBoundary, error fallback, onCaughtError, onUncaughtError, onRecoverableError, captureOwnerStack, FallbackProps, resetKeys
Applies to:
Handled elsewhere:
showBoundary(); retry and cache invalidation belong to whatever fetches.onError and the root handlers hand you an error and a component stack, and where those go is the reporting tool's concern.<philosophy>
A render error that no boundary catches unmounts the entire tree: React tears the root down rather than leave a half-rendered document on screen, so one thrown error anywhere becomes a blank page.
A boundary buys isolation against that: the blast radius is the subtree the nearest boundary wraps, so where the boundaries sit decides how much of the page a single bug costs. That makes placement the real decision — recovery, fallback wording and logging all follow from it.
Boundaries do not replace try/catch; they cover the one region try/catch cannot reach, which is React's own render.
</philosophy>
<patterns>
The two lifecycle methods split by phase: getDerivedStateFromError is pure and returns state, componentDidCatch is where reporting goes.
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
this.props.onError?.(error, errorInfo);
}Full code: examples/core.md
react-error-boundaryA FallbackComponent receives error and resetErrorBoundary, so the retry control lives wherever the design wants it. onError keeps reporting out of the fallback.
<ErrorBoundary FallbackComponent={ErrorFallback} onError={report}>
<Dashboard />
</ErrorBoundary>;
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}The prop table is in reference.md; full code in examples/core.md.
showBoundary() for async failuresAn async throw never reaches a boundary on its own. useErrorBoundary hands you the trigger.
const { showBoundary } = useErrorBoundary();
const load = async () => {
try {
const response = await fetch(endpoint);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
} catch (error) {
showBoundary(error);
}
};Use it where the failure should replace the subtree. Leave it alone where the error has inline UI of its own, such as a field-level validation message.
Full code: examples/core.md
resetKeys for automatic resetChanging any listed value clears the error and re-renders the children, which is what navigation and record-switching want.
<ErrorBoundary FallbackComponent={ErrorFallback} resetKeys={[currentPath]}>
<PageContent path={currentPath} />
</ErrorBoundary>Comparison is shallow, so an object or array key needs a stable reference or the boundary resets on every render.
Each widget gets its own boundary and its own fallback text; the root boundary catches whatever the inner ones do not.
<ErrorBoundary fallback={<p>Chart unavailable</p>} onError={report}>
<ChartWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Table unavailable</p>} onError={report}>
<DataTable />
</ErrorBoundary>One boundary around all three widgets means the first failure takes the other two with it.
There is an upper bound: the unit is the feature area a user would recognise as having failed on its own. A boundary around every component adds class components and fallback text nobody reads, and splits one failure into a page of small broken panels rather than one honest message.
Full code: examples/core.md
The fallback is UI a user meets at their worst moment: announce it, offer a way out, and keep the stack trace for development.
<div role="alert">
<h2>Something went wrong</h2>
{isDevelopment && <pre>{error.message}</pre>}
<button onClick={resetErrorBoundary}>Try again</button>
</div>A <span onClick> retry is unreachable by keyboard, and a raw stack in production leaks internals.
Full code: examples/core.md
createRoot takes three handlers that report rather than render, including for errors no boundary caught. They complement boundaries rather than replacing them.
const root = createRoot(container, {
onCaughtError: (error, info) => report("caught", error, info.componentStack),
onUncaughtError: (error, info) =>
report("uncaught", error, info.componentStack),
onRecoverableError: (error, info) =>
report("recoverable", error, info.componentStack),
});Full code: examples/react-19-hooks.md
</patterns>
<red_flags>
Breaks at runtime:
getDerivedStateFromError — it runs during render, so a fetch or a setState there breaks React's phase rules — report from componentDidCatch instead.resetKeys entry — the shallow compare sees a new array or object every render and resets the boundary continuously — memoise the value or key on a primitive.<span onClick={reset}> as the retry control — not focusable and not activated by Enter or Space — use <button>.Surprising behaviour:
showBoundary() they vanish.onCaughtError runs after the boundary's own componentDidCatch, not before it.onRecoverableError often carries the original throw on error.cause.captureOwnerStack() returns null outside development.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.