React Query
useApiError() and createRetry() — real error handling for the way most React apps actually fetch data.
Install
npm install @harshilrajput/universal-api-errors-react-querypnpm add @harshilrajput/universal-api-errors-react-queryyarn add @harshilrajput/universal-api-errors-react-query@tanstack/react-query is an optional peer dependency — this package's types are structural, so
it works with v4 or v5 without pinning either. The core package comes along automatically.
Why this isn't a parser
React Query doesn't invent its own error format. Whatever your queryFn throws — an
AxiosError, a failed fetch Response, anything — lands unchanged in query.error. So unlike
the Axios and fetch adapters, there's no wire shape to parse here.
What was actually missing was ergonomics: pulling a normalized UniversalError out of a query
result, and making React Query's retry behavior respect real error classification instead of
retrying every error — including a 404 that will never succeed — the same fixed number of times.
useApiError
import { useQuery } from "@tanstack/react-query";
import { useApiError } from "@harshilrajput/universal-api-errors-react-query";
function UserProfile({ id }: { id: string }) {
const query = useQuery({ queryKey: ["user", id], queryFn: () => fetchUser(id) });
const error = useApiError(query);
if (error?.isUnauthorized) return <RedirectToLogin />;
if (error) return <ErrorBanner message={error.message} />;
return <Profile user={query.data} />;
}Works identically with a useMutation result. The parse is memoized against isError/error,
so it doesn't re-run on every render while the query is settled.
createRetry
React Query's default retry: 3 retries any thrown error the same way. createRetry only
retries what UniversalError classifies as retryable — network failures, timeouts, rate limits,
5xx servers — up to a limit, and gives up immediately on everything else:
import { QueryClient } from "@tanstack/react-query";
import { createRetry } from "@harshilrajput/universal-api-errors-react-query";
const client = new QueryClient({
defaultOptions: {
queries: { retry: createRetry({ maxRetries: 3 }) },
},
});Override the classification with shouldRetry:
createRetry({
maxRetries: 5,
shouldRetry: (error, failureCount) => error.isNetwork || error.isTimeout,
});Everything from core, too
This package re-exports the entire core API, so parseError,
createUniversalError, the UniversalError class, retry, and every type are all available
from this one import.
A note on Server Components
useApiError is a hook — the package is marked "use client" and needs a client component
boundary. createRetry and the re-exported core functions have no such requirement and work
fine in a Server Component, e.g. when configuring a QueryClient for SSR prefetching.