T
TanStack2y ago
sunny-green

Disable error component, or enable enable it to retry query.

Hey, I got stuck with the problem that I want to have try again button to try to refetch query if it fails. I don't know how to do it using tanstack router because it is providing context inside error component, and i haven't found a way to disable errorComponent. here is my route code:
export const Route = createFileRoute('/team/$teamName/project')({
loader: (opts) => {
return opts.context.queryClient.ensureQueryData(
projectsQueryOptions(opts.params.teamName),
);
},
pendingComponent: () => <Loader size="lg" center />,
component: Projects,
});
export const Route = createFileRoute('/team/$teamName/project')({
loader: (opts) => {
return opts.context.queryClient.ensureQueryData(
projectsQueryOptions(opts.params.teamName),
);
},
pendingComponent: () => <Loader size="lg" center />,
component: Projects,
});
how i'd like to do error handling inside Projects component, or if it is possible to do it I'm fine to placing ErrorMessage component to errorComponent inside route definition.
const { data, error, refetch } = useSuspenseQuery(
projectsQueryOptions(teamName),
);

if (error) {
return <ErrorMessage error={error} reset={() => void refetch()} />;
}
const { data, error, refetch } = useSuspenseQuery(
projectsQueryOptions(teamName),
);

if (error) {
return <ErrorMessage error={error} reset={() => void refetch()} />;
}
8 Replies
flat-fuchsia
flat-fuchsia2y ago
is the issue that ensureQueryData throws an error and this causes the error component to be rendered? if yes, you could call it with throwOnError: false
sunny-green
sunny-greenOP2y ago
It doesn't work, I still get default errorComponent from router So what i figured out is:
function Error({error}: {error: Error}) {
return (
<ErrorMessage error={error} reset={router.invalidate} />
);
}
function Error({error}: {error: Error}) {
return (
<ErrorMessage error={error} reset={router.invalidate} />
);
}
and in Route:
errorComponent: (({error}) => {
if(isAxiosError(error)) {
return <Error error={error} />
}

return (
<ErrorComponent error={error} />
)
}),
errorComponent: (({error}) => {
if(isAxiosError(error)) {
return <Error error={error} />
}

return (
<ErrorComponent error={error} />
)
}),
But clicking on reset still does not fires any loading indicator, if it fails again from user perspective nothing has happend on clicking "reset"
flat-fuchsia
flat-fuchsia2y ago
it would be best if you can provide a minimal example on e.g. codesandbox
sunny-green
sunny-greenOP2y ago
Oke, on it. Here is what I have now: It does not work for some reason on code sandbox for me, but i download this code na locally it runs good. https://codesandbox.io/p/devbox/cocky-carson-xvyg3s?file=%2Fsrc%2Froutes%2Findex.tsx%3A7%2C1 Ideally I would love to not having to use errorComponent and just handle error inside my component in this case Post if it is possible. What I don't like about current solutions is lack of any loading indicator after clicking on try again.
flat-fuchsia
flat-fuchsia2y ago
The problem is that useSuspenseQuerythrows an error that is not caught by you and thus the error component is rendered. In order to catch this error, you need an error boundary. You could use something like this:
import React from "react";
import {
CatchBoundary,
createFileRoute,
} from "@tanstack/react-router";

import { router } from "../../main";
import { postQueryOptions } from "../../postQueryOptions";
import { useSuspenseQuery } from "@tanstack/react-query";

export const Route = createFileRoute("/post/$title")({
loader: async (opts) => {
try {
await opts.context.queryClient.ensureQueryData(
postQueryOptions(opts.params.title)
);
} catch (e) {
console.error("###", e);
}
},
pendingComponent: () => <div>Loading</div>,
component: Post,
});

function Error({ error }: { error: Error }) {
return (
<div>
Error
<button
onClick={() => {
void router.load();
}}
>
Try again{" "}
</button>
</div>
);
}

function PostInner() {
const { title } = Route.useParams();
const { data } = useSuspenseQuery(postQueryOptions(title));

return (
<CatchBoundary
getResetKey={() => "reset"}
onCatch={(error) => console.error(error)}
errorComponent={Error}
>
<div>
<h1>{title}</h1>
<p>{`${data}`}</p>
</div>
</CatchBoundary>
);
}
function Post() {
return (
<CatchBoundary
getResetKey={() => "reset"}
onCatch={(error) => console.error(error)}
errorComponent={Error}
>
<PostInner />
</CatchBoundary>
);
}
import React from "react";
import {
CatchBoundary,
createFileRoute,
} from "@tanstack/react-router";

import { router } from "../../main";
import { postQueryOptions } from "../../postQueryOptions";
import { useSuspenseQuery } from "@tanstack/react-query";

export const Route = createFileRoute("/post/$title")({
loader: async (opts) => {
try {
await opts.context.queryClient.ensureQueryData(
postQueryOptions(opts.params.title)
);
} catch (e) {
console.error("###", e);
}
},
pendingComponent: () => <div>Loading</div>,
component: Post,
});

function Error({ error }: { error: Error }) {
return (
<div>
Error
<button
onClick={() => {
void router.load();
}}
>
Try again{" "}
</button>
</div>
);
}

function PostInner() {
const { title } = Route.useParams();
const { data } = useSuspenseQuery(postQueryOptions(title));

return (
<CatchBoundary
getResetKey={() => "reset"}
onCatch={(error) => console.error(error)}
errorComponent={Error}
>
<div>
<h1>{title}</h1>
<p>{`${data}`}</p>
</div>
</CatchBoundary>
);
}
function Post() {
return (
<CatchBoundary
getResetKey={() => "reset"}
onCatch={(error) => console.error(error)}
errorComponent={Error}
>
<PostInner />
</CatchBoundary>
);
}
sunny-green
sunny-greenOP2y ago
Thx for your answer it does work, and I can handle it inside component. But what about some loading indicator to show user that clicking on try again actually did something. Now if request fails again it looks like button did nothing.
flat-fuchsia
flat-fuchsia2y ago
This is how you would do it using react-query's mechanism:
import React, {Suspense} from "react";
import {
CatchBoundary,
createFileRoute,
useNavigate,
} from "@tanstack/react-router";

import {
useSuspenseQuery,
QueryErrorResetBoundary,
} from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';

import { router } from "../../main";
import { postQueryOptions } from "../../postQueryOptions";

function Pending() {
return (<div>Loading</div>)
}
export const Route = createFileRoute("/post/$title")({
loader: async (opts) => {
await opts.context.queryClient.ensureQueryData(
postQueryOptions(opts.params.title)
);
},
pendingComponent: Pending,
component: Post,
});

function PostInner() {
const { title } = Route.useParams();
const { data } = useSuspenseQuery(postQueryOptions(title));

return (
<div>
<h1>{title}</h1>
<p>{`${data}`}</p>
</div>
);
}
function Post() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => {
return (
<div>
There was an error!
<button onClick={() => resetErrorBoundary()}>
Try again
</button>
</div>
);
}}
>
<Suspense fallback={<Pending />}>
<PostInner />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
import React, {Suspense} from "react";
import {
CatchBoundary,
createFileRoute,
useNavigate,
} from "@tanstack/react-router";

import {
useSuspenseQuery,
QueryErrorResetBoundary,
} from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';

import { router } from "../../main";
import { postQueryOptions } from "../../postQueryOptions";

function Pending() {
return (<div>Loading</div>)
}
export const Route = createFileRoute("/post/$title")({
loader: async (opts) => {
await opts.context.queryClient.ensureQueryData(
postQueryOptions(opts.params.title)
);
},
pendingComponent: Pending,
component: Post,
});

function PostInner() {
const { title } = Route.useParams();
const { data } = useSuspenseQuery(postQueryOptions(title));

return (
<div>
<h1>{title}</h1>
<p>{`${data}`}</p>
</div>
);
}
function Post() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => {
return (
<div>
There was an error!
<button onClick={() => resetErrorBoundary()}>
Try again
</button>
</div>
);
}}
>
<Suspense fallback={<Pending />}>
<PostInner />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
https://codesandbox.io/p/devbox/inspiring-field-rtjtsk
sunny-green
sunny-greenOP2y ago
Thanks!

Did you find this page helpful?