Guides
Retries and errors
Retry failed requests with the retry() link, set attempts per query in meta, choose delays and which errors to retry, time requests out, and show errors.
The retry() link retries failed requests. With its defaults, a request that fails in the browser is retried 3 times, waiting 1, 2 and then 4 seconds, before the query counts as failed. Readers keep suspending, or keep showing their current data, the whole time.
Nothing retries unless the client has the link, on its own or through recommended():
const const queryClient: QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
queryClient = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ links?: (readonly AnyLink[] & readonly [Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry()] });
// or
const const withEverything: QueryClient<{
readonly meta: Flat<Present<RetryMeta> & Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta>>;
}>
withEverything = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>, Feature<{
readonly meta: ReconnectRefetchMeta;
}>, Feature<{
readonly meta: PollingMeta;
}>]>(config?: QueryClientConfig & {
readonly features?: readonly [Feature<{
readonly meta: FocusRefetchMeta;
}>, Feature<{
readonly meta: ReconnectRefetchMeta;
}>, Feature<...>] | undefined;
readonly links?: readonly [...] | undefined;
}): QueryClient<...>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ ...function recommended(): RecommendedThe behaviors most apps want: retries, refetching on focus and reconnect,
and polling. Spread it into the config:
recommended() });
Some requests are never retried:
- Cancelled requests, whose
signalwas aborted. - Requests on a server client. The user is waiting for the HTML, and the browser can try again.
Attempts per query
A query sets its own attempts with meta: { retry }: a number of retries after the first attempt, or false for none. It wins over the link's attempts:
export function function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery(input: {
slug: string;
}
input: { slug: stringslug: string }) {
return queryOptions<readonly ["posts", "bySlug", string], Post, Post>(options: BaseOptions<readonly ["posts", "bySlug", string], DefaultTypes> & {
readonly queryFn: (context: QueryFunctionContext<readonly ["posts", "bySlug", string]>) => Awaitable<Post>;
readonly select?: ((data: Post) => Post) | undefined;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes> (+1 overload)
Typed options for a query factory:
queryOptions({
BaseOptions<readonly ["posts", "bySlug", string], DefaultTypes>.queryKey: readonly ["posts", "bySlug", string]A plain array, usually built by a group(). Handles keep its literal type.
queryKey: posts<readonly ["bySlug", string]>(rest_0: "bySlug", rest_1: string): readonly ["posts", "bySlug", string]posts("bySlug", input: {
slug: string;
}
input.slug: stringslug),
queryFn: (context: QueryFunctionContext<readonly ["posts", "bySlug", string]>) => Awaitable<Post>queryFn: () => function getPost(slug: string): Promise<Post>getPost(input: {
slug: string;
}
input.slug: stringslug),
BaseOptions<readonly ["posts", "bySlug", string], DefaultTypes>.meta?: NoInfer<Partial<Readonly<Record<string, unknown>>>> | undefinedSettings for the root's links and features, and data for targets: the
fields the root's parts declare, such as retry with the retry()
link.
meta: { retry: numberretry: 5 }, // or `false`
});
}
The retry() link declares the field, so a factory built with a bound queryOptions gets it typed only when the root has the link: see Query meta. On a client without the link, meta.retry does nothing.
Options
retry({ attempts, delay, when }) sets the defaults for every query of the client:
| Option | Default |
|---|---|
attempts | 3: retries after the first attempt |
delay | Doubling from 1 second, at most 30 seconds |
when | Every error but a thrown Response: whether an error is worth retrying |
delay(attempt) gets the retry's index, from 0. when(error) is the place to skip errors that won't go away:
const const patient: QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
patient = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({
links?: (readonly AnyLink[] & readonly [Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [
function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry({
RetryOptions.attempts?: number | undefinedRetries after the first attempt. A query's meta.retry wins. Default 3.
attempts: 5,
RetryOptions.delay?: ((attempt: number) => number) | undefinedWait before retry n (0-based), in ms. Default: doubling from 1 s, at most 30 s.
delay: (attempt: numberattempt) => 500 * (attempt: numberattempt + 1),
RetryOptions.when?: ((error: unknown) => boolean) | undefinedWhether an error is worth retrying. Default: every error but a thrown Response.
when: (error: unknownerror) => !(error: unknownerror instanceof class HttpErrorHttpError && error: HttpErrorerror.HttpError.status: numberstatus === 404),
}),
],
});
Retries while offline
In the browser, a retry waits while navigator.onLine is false: after the delay, it waits for the window's online event. The query counts as fetching meanwhile. The first attempt always runs.
Timeouts
timeout(ms) is another link: it fails a request that takes longer than ms with a QueryTimeoutError, and aborts its signal. Links wrap each other in order, outermost first, so where it goes decides what it times:
// Each attempt gets 10 seconds.
const const perAttempt: QueryClient<{
readonly meta: Flat<Present<RetryMeta> & Present<object>>;
}>
perAttempt = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>, Link], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
readonly meta: RetryMeta;
}>, Link] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta> & Present<object>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ links?: (readonly AnyLink[] & readonly [Link<{
readonly meta: RetryMeta;
}>, Link]) | undefined
Wrap every request, outermost first.
links: [function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry(), function timeout(ms: number): LinkFail a request (and abort its signal) when it takes longer than ms.
timeout(10_000)] });
// The request and all its retries get 30 seconds.
const const overall: QueryClient<{
readonly meta: Flat<Present<RetryMeta> & Present<object>>;
}>
overall = createQueryClient<readonly [Link, Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link, Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta> & Present<object>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ links?: (readonly AnyLink[] & readonly [Link, Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [function timeout(ms: number): LinkFail a request (and abort its signal) when it takes longer than ms.
timeout(30_000), function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry()] });
Showing errors
What the user sees depends on whether there was data before the failure.
The first load fails. Reading the query throws to the nearest error boundary. Before the boundary renders its children again, start the query again the way the loader did, so the reader suspends on the new request instead of throwing the old error:
function function CommentsSection(): React.JSX.ElementCommentsSection() {
return (
<class CatchBoundaryCatchBoundary
getResetKey: () => unknowngetResetKey={() => const loaderData: {
comments: QueryHandle<typeof commentsQuery>;
}
loaderData.comments: QueryHandle<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>
comments.QueryHandle<(input: { slug: string; }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>.input: {
slug: string;
}
input.slug: stringslug}
errorComponent?: ErrorRouteComponent | undefinederrorComponent={function CommentsError(props: ErrorComponentProps): React.JSX.ElementCommentsError}
>
<const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense React.SuspenseProps.fallback?: ReactNodeA fallback react tree to show when a Suspense child (like React.lazy) suspends
fallback={<function CommentsSkeleton(): ReactNodeCommentsSkeleton />}>
<function Comments(props: {
readonly handle: QueryHandle<typeof commentsQuery>;
}): ReactNode
Comments handle: QueryHandle<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>
handle={const loaderData: {
comments: QueryHandle<typeof commentsQuery>;
}
loaderData.comments: QueryHandle<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>
comments} />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense>
</class CatchBoundaryCatchBoundary>
);
}
function function CommentsError(props: ErrorComponentProps): React.JSX.ElementCommentsError(props: ErrorComponentPropsprops: type ErrorComponentProps<TError = unknown> = {
error: TError;
info?: {
componentStack: string;
};
reset: () => void;
}
ErrorComponentProps) {
const const queryClient: QueryClient<any>queryClient = function useQueryClient(): QueryClientThe client from the nearest
QueryClientProvider.
useQueryClient();
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.role?: React.AriaRole | undefinedrole="alert">
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
Couldn't load comments:{" "}
{props: ErrorComponentPropsprops.error: unknownerror instanceof var Error: ErrorConstructorError
? props: ErrorComponentPropsprops.error: Errorerror.Error.message: stringmessage
: var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(props: ErrorComponentPropsprops.error: unknownerror)}
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => {
void load<{
readonly comments: LoadEntry<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">;
}>(client: QueryClient, entries: {
readonly comments: LoadEntry<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">;
}): Promise<...>
Start a loader's queries and return a serializable handle for each. It
starts every entry before it waits for any, so the order of the entries
can't create a waterfall. Resolves with [handles, data] once every
swr and revalidate entry has data, and rejects with the error as soon
as one fails.
prefetch entries never make it reject. A skipToken in place of an
entry starts nothing and gets no handle.
load(const queryClient: QueryClient<any>queryClient, {
comments: LoadEntry<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, "prefetch">
comments: prefetch<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>(factory: (input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>, ...args: LoadEntryArgs<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>): LoadEntry<...>
Start the query if the cache has no fresh data, and don't wait for it.
It never makes the load reject: errors show up where a component reads
the handle.
prefetch(function commentsQuery(input: {
slug: string;
}): InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>
commentsQuery, const loaderData: {
comments: QueryHandle<typeof commentsQuery>;
}
loaderData.comments: QueryHandle<(input: {
slug: string;
}) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>
comments.QueryHandle<(input: { slug: string; }) => InfiniteQueryOptions<readonly ["posts", "comments", string], CommentPage, number, InfiniteData<CommentPage, number>, DefaultTypes>>.input: {
slug: string;
}
input),
});
props: ErrorComponentPropsprops.reset: () => voidreset();
}}
>
Try again
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}Any error boundary works: CatchBoundary from TanStack Router, react-error-boundary, Next.js's error.tsx, or your own class component.
A background refetch fails. The existing data stays on screen: losing it because a refetch failed would be worse than showing it a little longer. The error is on the reader's state as error, for a hint:
const [const postThenable: QueryValue<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
postThenable, const controls: QueryState<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
controls] = useQuery<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>(factory: (input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>, handle: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>, options?: UseQueryOptions): readonly [...]
Read a query by its handle.
useQuery(function postQuery(input: {
slug: string;
}): QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>
postQuery, const loaderData: {
post: QueryHandle<typeof postQuery>;
}
loaderData.post: QueryHandle<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
post);
const const post: Postpost = use<Post>(usable: React.Usable<Post>): Postuse(const postThenable: QueryValue<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
postThenable);
{
const controls: QueryState<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
controls.QueryState<(input: { slug: string; }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>.error: unknownThe last request's error while it's the latest outcome.
error ? (
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<HTMLParagraphElement>.role?: React.AriaRole | undefinedrole="status">
Couldn't refresh. Showing data from {function formatTime(value: number): stringformatTime(const controls: QueryState<(input: {
slug: string;
}) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>
controls.QueryState<(input: { slug: string; }) => QueryOptions<readonly ["posts", "bySlug", string], Post, Post, DefaultTypes>>.updatedAt: numberWhen the data was fetched (ms since epoch); 0 without data.
updatedAt)}.
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
) : null;
}Make the next request fail in the demo, then reload a post to see each case:
Errors from loaders
A failed prefetch never makes load reject: the error surfaces where the data is read, and only that part of the page shows it. A failed swr or revalidate makes load reject, so it reaches the route's error boundary (errorComponent in TanStack Router, error.tsx in Next.js), replacing the page.
This is one more reason to choose per query: wait with swr for what the page can't do without, and prefetch the rest, so a failing sidebar doesn't take the whole page down.
One place for every error
A link sees every request of the client, so it's the place for logging and toasts. Put it before retry() to see each request's final error, after its retries. It can declare a meta field of its own, for a per-query message:
const const reportErrors: Link<{
meta: {
errorMessage?: string;
};
}>
reportErrors = link<{
meta: {
errorMessage?: string;
};
}>(run: (op: Operation<Present<{
errorMessage?: string;
}> & object>, next: (op: Operation) => Promise<unknown>) => Promise<unknown>): Link<{
meta: {
errorMessage?: string;
};
}>
A link that declares the meta fields it reads. A root's queries take
those fields only when the root has the link.
link<{ meta: {
errorMessage?: string;
}
meta: { errorMessage?: string | undefinederrorMessage?: string } }>(
async (op: Operation<Present<{
errorMessage?: string;
}> & object>
op, next: (op: Operation) => Promise<unknown>next) => {
try {
return await next: (op: Operation) => Promise<unknown>next(op: Operation<Present<{
errorMessage?: string;
}> & object>
op);
} catch (function (local var) error: unknownerror) {
if (!op: Operation<Present<{
errorMessage?: string;
}> & object>
op.Operation<TMeta extends object = object>.signal: AbortSignalsignal.AbortSignal.aborted: booleanThe aborted read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (true) or not (false).
aborted) {
function reportError(error: unknown, context: {
readonly queryKey: unknown;
}): void
reportError(function (local var) error: unknownerror, { queryKey: unknownqueryKey: op: Operation<Present<{
errorMessage?: string;
}> & object>
op.Operation<TMeta extends object = object>.queryKey: QueryKeyqueryKey });
function showToast(message: string): voidshowToast(op: Operation<Present<{
errorMessage?: string;
}> & object>
op.Operation<Present<{ errorMessage?: string; }> & object>.meta: Present<{
errorMessage?: string;
}> & object
The query's meta, {} when it has none.
meta.errorMessage: string | undefinederrorMessage ?? "Something went wrong");
}
throw function (local var) error: unknownerror;
}
},
);
const const reporting: QueryClient<{
readonly meta: Flat<Present<{
errorMessage?: string;
}> & Present<RetryMeta>>;
}>
reporting = createQueryClient<readonly [Link<{
meta: {
errorMessage?: string;
};
}>, Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
meta: {
errorMessage?: string;
};
}>, Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<{
errorMessage?: string;
}> & Present<RetryMeta>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({ links?: (readonly AnyLink[] & readonly [Link<{
meta: {
errorMessage?: string;
};
}>, Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [const reportErrors: Link<{
meta: {
errorMessage?: string;
};
}>
reportErrors, function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry()] });
Redirects
In TanStack Start, a queryFn can throw the router's redirect(), for instance when a session has expired. setupRouterIntegration turns it into a navigation wherever the query failed: a loader, a render or a background refetch.
A redirect is a thrown Response, which retry() doesn't retry, so it navigates at once. A when of your own replaces that check, so keep it:
const const withRedirects: QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
withRedirects = createQueryClient<readonly [Link<{
readonly meta: RetryMeta;
}>], []>(config?: QueryClientConfig & {
readonly features?: [] | undefined;
readonly links?: readonly [Link<{
readonly meta: RetryMeta;
}>] | undefined;
}): QueryClient<{
readonly meta: Flat<Present<RetryMeta>>;
}>
Create a query client. Behaviors are opt-in: pass links and features,
or spread recommended(). The client's meta type is what they declare.
createQueryClient({
links?: (readonly AnyLink[] & readonly [Link<{
readonly meta: RetryMeta;
}>]) | undefined
Wrap every request, outermost first.
links: [
function retry(options?: RetryOptions): Link<{
readonly meta: RetryMeta;
}>
Retry failed requests. Thrown Responses (router redirects and
not-founds) and cancelled requests aren't retried,
server clients never retry, and a browser that is offline
waits until it's back online before retrying. A query sets its own
attempts with meta: { retry }.
retry({
RetryOptions.when?: ((error: unknown) => boolean) | undefinedWhether an error is worth retrying. Default: every error but a thrown Response.
when: (error: unknownerror) =>
!function isRedirect(obj: any): obj is AnyRedirectCheck whether a value is a TanStack Router redirect Response.
isRedirect(error: unknownerror) &&
!(error: unknownerror instanceof class HttpErrorHttpError && error: HttpErrorerror.HttpError.status: numberstatus === 404),
}),
],
});