ApiErrorBoundary
<ApiErrorBoundary> is a small class-based error boundary that catches errors thrown during render, types them as ApiError | unknown, and lets you reset back to a clean state.
It’s the partner to useSuspenseQ / useSuspenseInfiniteQuery. Throwing a Promise (suspending) propagates to <Suspense>; throwing an Error propagates here.
Basic usage
Section titled “Basic usage”import { Suspense } from "react";import { ApiErrorBoundary, isApiError } from "@use-q/api-client-react";import { useSuspenseQ } from "@/api/client";
function PostList({ facilityId }: { facilityId: string }) { const { data } = useSuspenseQ("listPosts", { pathParams: { facilityId } }); return data.map((p) => <article key={p.id}>{p.title}</article>);}
export function Page({ facilityId }: { facilityId: string }) { return ( <ApiErrorBoundary fallback={(error, reset) => ( <div role="alert"> {isApiError(error) ? `${error.status} — ${error.message}` : "Something went wrong."} <button onClick={reset}>Retry</button> </div> )} > <Suspense fallback={<p>Loading posts…</p>}> <PostList facilityId={facilityId} /> </Suspense> </ApiErrorBoundary> );}interface ApiErrorBoundaryProps { children: React.ReactNode; fallback: (error: unknown, reset: () => void) => React.ReactNode; onError?: (error: unknown) => void; onReset?: () => void;}fallback
Section titled “fallback”Called whenever a render-time error is caught. You receive the raw error and a reset function.
Use isApiError to narrow the error to your domain shape:
<ApiErrorBoundary fallback={(err, reset) => { if (isApiError<ApiProblem>(err)) { if (err.status === 404) return <NotFound />; if (err.status === 403) return <Forbidden />; return <ProblemAlert problem={err.parsed} onRetry={reset} />; } return <UnknownError onRetry={reset} />; }}> {/* … */}</ApiErrorBoundary>onError
Section titled “onError”A side-effecting hook that fires once per caught error. Use it for telemetry:
<ApiErrorBoundary onError={(err) => { Sentry.captureException(err); }} fallback={(err, reset) => <ErrorScreen error={err} onRetry={reset} />}>onReset
Section titled “onReset”Called when the user (or your code) invokes reset. Use it to clear adjacent state or invalidate related caches.
<ApiErrorBoundary onReset={() => qc.invalidateAll()} fallback={(err, reset) => <ErrorScreen onRetry={reset} />}>reset() semantics
Section titled “reset() semantics”reset() does exactly two things:
- Clears the boundary’s internal error state, causing it to re-render its
children. - Calls your optional
onResetcallback.
It does not automatically refetch. The next render will resume normally — if the underlying query is still in an errored state, the suspense child will throw again immediately. The typical pattern is to pair reset with a cache invalidation:
<ApiErrorBoundary fallback={(err, reset) => { const qc = useQClient(); return ( <button onClick={() => { void qc.invalidateAll(); reset(); }} > Try again </button> ); }}>Or wire it up via onReset:
function PageBoundary({ children }: { children: React.ReactNode }) { const qc = useQClient(); return ( <ApiErrorBoundary onReset={() => qc.invalidateAll()} fallback={(err, reset) => ( <button onClick={reset}>Try again</button> )} > {children} </ApiErrorBoundary> );}Nesting
Section titled “Nesting”You can nest boundaries to make some parts of the page fail in isolation:
<ApiErrorBoundary fallback={() => <PageError />}> <Suspense fallback={<PageSkeleton />}> <Header /> <ApiErrorBoundary fallback={() => <CommentsUnavailable />}> <Suspense fallback={<CommentsSkeleton />}> <Comments postId={postId} /> </Suspense> </ApiErrorBoundary> </Suspense></ApiErrorBoundary>The inner boundary catches errors from <Comments /> only; everything else keeps rendering.
Narrowing with isApiError
Section titled “Narrowing with isApiError”isApiError is a generic type guard — pass the parsed-error type for full inference:
import { isApiError } from "@use-q/api-client-react";
interface ApiProblem { type: string; title: string; detail: string;}
fallback={(err, reset) => { if (isApiError<ApiProblem>(err)) { return ( <article> <h2>{err.parsed.title}</h2> <p>{err.parsed.detail}</p> <button onClick={reset}>Retry</button> </article> ); } return <p>Unknown error</p>;}}