Error Boundary
ErrorBoundary catches errors thrown during the render of any descendant and shows a fallback instead of a blank screen. useErrorHandler extends that to asynchronous errors, which React does not catch on its own.
Why this matters
Without a boundary, a render error in any component unmounts the whole tree — the user sees a blank page. A boundary isolates the failure: the broken branch turns into a friendly fallback (with a retry button) and the rest of the app stays up.
Typical usage
fallback accepts a static node or a render-prop receiving { error, reset }. The render-prop is the recommended path — you show the message and offer a retry button that clears the boundary:
import { useEffect } from "react";
import { ErrorBoundary, ErrorState } from "tempest-react-sdk";
import { useLocation } from "react-router";
export function Shell({ children }: { children: React.ReactNode }) {
const location = useLocation();
return (
<ErrorBoundary
resetKeys={[location.pathname]}
onError={(error, info) => reportToSentry(error, info)}
fallback={({ error, reset }) => (
<ErrorState title="Something went wrong" description={error.message} onRetry={reset} />
)}
>
{children}
</ErrorBoundary>
);
}
declare function reportToSentry(error: Error, info: React.ErrorInfo): void;
The three props that matter:
fallback: a static node or({ error, reset }) => ReactNode.erroris the capturedError;reset()clears the state and re-renders the children.onError(error, info): called fromcomponentDidCatch. Forward it to your tracker (Sentry, Datadog, console).info.componentStackshows where it blew up.resetKeys: an array watched withObject.isequality. When any value changes, the boundary resets itself.
resetKeys clears the error on navigation
By passing resetKeys={[location.pathname]}, the boundary recovers automatically when the user changes route — without it, the fallback would stay stuck on screen until a manual reset().
Asynchronous errors
Boundaries only catch errors thrown during render. Failures in callbacks, timers, websockets or promises happen outside render, so React never sees them. useErrorHandler fixes this: it re-throws the error inside a setState, forcing a render pass that the boundary above intercepts.
import { useEffect } from "react";
import { useErrorHandler } from "tempest-react-sdk";
export function LiveFeed({ socket }: { socket: WebSocket }) {
const throwError = useErrorHandler();
useEffect(() => {
const onError = (event: Event) => throwError(event);
socket.addEventListener("error", onError);
return () => socket.removeEventListener("error", onError);
}, [socket, throwError]);
return <Feed />;
}
Accepts any value
throwError takes unknown and normalizes it to Error: if you pass something that is not an Error (a string, an object), it wraps it in new Error(String(value)) before propagating.
How AppProviders mounts the boundary
AppProviders already places the ErrorBoundary as the outermost layer (above Query, Theme and i18n), so an error from any provider or from the UI itself lands in the fallback. It is opt-in: the boundary only mounts if you pass errorBoundary with a fallback.
import { AppProviders, ErrorState } from "tempest-react-sdk";
<AppProviders
errorBoundary={{
fallback: ({ error, reset }) => <ErrorState description={error.message} onRetry={reset} />,
onError: (error, info) => reportToSentry(error, info),
}}
i18n={{ locale: "pt-BR", messages }}
>
<App />
</AppProviders>;
The internal order is ErrorBoundary → QueryProvider → ThemeProvider → I18nProvider, i.e. the global boundary wraps everything.
Where to place boundaries
- One global boundary in the app shell (via
AppProvidersor manually), with a generic fallback — the safety net. - Local boundaries around optional widgets (side feed, charts, embeds) so an isolated error does not take down the whole screen. Each local boundary can have its own
fallbackandresetKeys.
Recap
ErrorBoundarycatches render errors;fallbackis a static node or a({ error, reset })render-prop.onError(error, info)forwards to telemetry;info.componentStacklocates the failure.resetKeysresets the boundary when any value (byObject.is) changes — great withlocation.pathname.useErrorHandler()covers async errors by re-throwing into render; it acceptsunknownand normalizes toError.AppProvidersmounts the boundary as the outermost layer when you passerrorBoundary={{ fallback }}.- Combine a global boundary with local boundaries around optional widgets.
See also
- Components — ErrorState — a ready-made fallback with retry
- App Providers — where the global boundary is mounted
- Logger / Telemetry — destinations for
onError