universal-api-errors

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-query
pnpm add @harshilrajput/universal-api-errors-react-query
yarn 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.

On this page