What is the onRecoverableError option of React's hydrateRoot and createRoot, when does React call it, and what does "recoverable" mean for the page the user is looking at?
answer
- React already handled it
- the user sees nothing wrong
- reporting hook, not a handling hook
- component stack arrives as the second argument
- default route is the browser's reportError
basics
~20 sonRecoverableError is an option of hydrateRoot and createRoot that React calls when it hit an error and recovered by itself — typically a hydration error it fixed by re-rendering that content on the client. The UI is correct, so wire it to your error reporter.
solid answer
~50 s`onRecoverableError` is the reporting channel for errors React handled without your help. You pass it in the options object to `hydrateRoot` or `createRoot`, and React calls it with the error and a second argument carrying a `componentStack`. The classic trigger is a hydration error: React cannot adopt the server markup for some part of the tree, so it discards that markup and renders the content on the client instead. "Recoverable" means exactly that — the user ends up looking at a correct UI, nothing crashes, no error boundary is involved. That is also why it is dangerous: the failure is invisible in production unless you report it. If you pass nothing, React reports through the browser's `reportError`, so it surfaces in the console and to `window` error handlers but not to your backend. In practice I wire it to the error tracker with sampling, because one bad component fires across every session.
code
javascript · 19 linesimport { hydrateRoot } from 'react-dom/client';
import App from './App';
let reported = 0;
hydrateRoot(document.getElementById('root'), <App />, {
onRecoverableError(error, errorInfo) {
if (reported >= 5) return; // cap the volume per session
reported += 1;
navigator.sendBeacon(
'/client-errors',
JSON.stringify({
message: String(error),
componentStack: errorInfo.componentStack,
url: location.pathname,
}),
);
},
});go deeper
Know that React can hit a problem during hydration, fix it by rendering on the client, and tell you about it through a callback rather than showing the user anything.
Explain where the option is passed, what its two arguments are, and why "recoverable" means React already produced the correct UI without involving an error boundary.
Show the production instinct: this failure is invisible to users, so it needs sampled reporting with component stacks, release tags, and alerting on rate changes after a deploy.
Own the cost argument — a root that recovers this way is paying for server rendering and getting client rendering — and decide what error rate is acceptable before it becomes a release blocker.
## What the option is Both client root APIs accept an options object, and `onRecoverableError` is one of its callbacks: ```javascript import { hydrateRoot } from 'react-dom/client'; hydrateRoot(document.getElementById('root'), <App />, { onRecoverableError(error, errorInfo) { reportToTracker(error, errorInfo.componentStack); }, }); ``` React calls it with the error and an `errorInfo` object carrying a `componentStack` — the component path where the trouble occurred, which is usually the only thing that makes the report actionable. ## When React calls it The name is the specification: React calls it for errors it *already dealt with*. Two families matter. **Hydration errors.** React could not adopt the server's markup for some part of the tree. Rather than leave the user with broken output, it throws away the server HTML for the affected region and renders that content on the client, then reports through `onRecoverableError`. **Renders that succeeded on retry.** The concurrent renderer can attempt work, hit an error, and produce correct output on a second attempt. The user never sees a failure; you still get told one happened. ## What "recovered" actually looks like This is where candidates get vague, so be concrete. Recovery is not a retry loop and not a fallback UI. For a hydration error, React abandons the server-produced markup for the nearest `<Suspense>` boundary containing the problem — or for the whole root if there is no boundary — and client-renders that content instead. The final DOM matches what a pure client render would have produced. So two things are true at once. The UI is correct, and you have paid for it: the server rendering for that region was wasted, the browser re-created those nodes, and the region's contribution to early paint is gone. A page whose whole root recovers this way is, in effect, a client-rendered page that also paid for SSR. ## Not an error boundary The distinction is a favourite follow-up. An error boundary — a component implementing `getDerivedStateFromError` or `componentDidCatch` — handles errors React *cannot* recover from: rendering that subtree failed, and the boundary swaps in a fallback the user sees. `onRecoverableError` is the opposite case: React handled it, the user sees the intended UI, and nothing in your component tree is told anything. They are complementary channels, not alternatives, and React 19 rounds out the set with `onCaughtError` (an error a boundary caught) and `onUncaughtError` (one nothing caught). ## The default, and why you override it If you supply no callback, React reports the error through the browser's `reportError`, which puts it in the console and delivers it to global error handlers. That is fine locally and nearly useless in production: the users hitting the problem are not reading their consoles, and by definition nothing looks wrong to them. Hydration problems are one of the few classes of bug that can affect a large share of sessions and generate zero support tickets. ## Wiring it in production A few things earn their keep: - **Send it to the tracker with the component stack.** The stack is the difference between an actionable ticket and "hydration error on /home". - **Sample and deduplicate.** One mismatching component fires on every affected session, so an unsampled feed can drown a tracker and its quota. - **Tag the release and route.** These regressions arrive with a deploy and cluster on particular pages; without those tags the volume tells you nothing. - **Alert on rate, not on presence.** A trickle is normal noise on a large site; a step change after a release is the signal. ## Interview framing Define it as a *reporting* hook rather than a *handling* hook, name the callback's arguments, and say what recovery cost. The strongest closing point is the production one: this is a silent failure mode, and the only reason to configure the callback at all is to stop it being silent.
- How does onRecoverableError differ from an error boundary?An error boundary handles what React cannot: rendering that subtree failed, and the boundary shows a fallback the user sees. `onRecoverableError` fires for errors React already worked around, so the intended UI is on screen and no component is notified. One changes what is rendered; the other only tells you something happened.
- What does React do if you never pass onRecoverableError?It reports through the browser's `reportError`, so the error reaches the console and global error handlers. That is enough in development and close to useless in production, because the affected users see a correct page and never report anything — which is exactly why the callback exists.
- Would you forward every recovered error to your error tracker?Forward them, but sampled and deduplicated, with the component stack, route and release attached. A single misbehaving component fires on every affected session, so an unfiltered feed buries the signal and burns quota. Alert on a change in rate after a deploy rather than on any occurrence.
saying these in an interview costs you the question
- Thinks onRecoverableError renders a fallback like an error boundary
- Says the user sees an error message when it fires
- Assumes recovery is free and costs nothing at runtime
- Believes React swallows the error entirely with no report
- Treats console-only default reporting as production monitoring