T
TanStack•5mo ago
wise-white

Invalidating query/route after mutation

Hello! I am using TS router with TS query in a Vite SPA application. I have a page that lists all events like so:
import { eventsQueryOptions } from '../query-options';
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/events/')({
component: RouteComponent,
loader: async ({ context }) => await context.queryClient.ensureQueryData(eventsQueryOptions),
});

function RouteComponent() {
const events = Route.useLoaderData();

return (
<div>
<Link to='/events/new'>
<Button>Create new event</Button>
</Link>
<ul>
{events.map((event) => (<li key={event.uuid}>{event.name}</li>))}
</ul>
</div>
);
}
import { eventsQueryOptions } from '../query-options';
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/events/')({
component: RouteComponent,
loader: async ({ context }) => await context.queryClient.ensureQueryData(eventsQueryOptions),
});

function RouteComponent() {
const events = Route.useLoaderData();

return (
<div>
<Link to='/events/new'>
<Button>Create new event</Button>
</Link>
<ul>
{events.map((event) => (<li key={event.uuid}>{event.name}</li>))}
</ul>
</div>
);
}
And then a page that allows me to create a new event:
import { useMutation } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { createEvent } from '../events';
import { eventsQueryOptions } from '../query-options';
import { useQueryClient } from '@tanstack/react-query';

export const Route = createFileRoute('/events/new/')({
component: RouteComponent,
});

function RouteComponent() {
const queryClient = useQueryClient();

const mutation = useMutation({
mutationFn: createEvent,
onSuccess: async (response) => {
await queryClient.invalidateQueries({ queryKey: eventsQueryOptions.queryKey });
await router.invalidate();
await router.navigate({ to: '/events' });
}
});

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = {
name: formData.get('name') as string,
};
mutation.mutate(data);
};

return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />

<input type="submit" />

{mutation.isPending && <p>Submitting...</p>}
{mutation.isError && <p>Error!</p>}
{mutation.isSuccess && <p>Submitted successfully!</p>}
</form>
);
}
import { useMutation } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { createEvent } from '../events';
import { eventsQueryOptions } from '../query-options';
import { useQueryClient } from '@tanstack/react-query';

export const Route = createFileRoute('/events/new/')({
component: RouteComponent,
});

function RouteComponent() {
const queryClient = useQueryClient();

const mutation = useMutation({
mutationFn: createEvent,
onSuccess: async (response) => {
await queryClient.invalidateQueries({ queryKey: eventsQueryOptions.queryKey });
await router.invalidate();
await router.navigate({ to: '/events' });
}
});

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = {
name: formData.get('name') as string,
};
mutation.mutate(data);
};

return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />

<input type="submit" />

{mutation.isPending && <p>Submitting...</p>}
{mutation.isError && <p>Error!</p>}
{mutation.isSuccess && <p>Submitted successfully!</p>}
</form>
);
}
The problem is that it is not invalidating after a new event is created! When I create a new event the list remains unchanged. What am I doing wrong?
8 Replies
like-gold
like-gold•5mo ago
what exactly does not invalidate? you should probably use a query hook instead of useLoaderData like useSuspenseQuery because useLoaderData will not be updated when the query refetches in the background
wise-white
wise-whiteOP•5mo ago
It does not show the updated events list when I go back to the events page. Hummm so I should ensureQueryData on the loader, and then access it on the route using useSuspenseQuery?
like-gold
like-gold•5mo ago
yes this is how we do it in all our query based examples
wise-white
wise-whiteOP•5mo ago
Ok I'm using that and now it works! But I see a problem: now whenever I go to the events page (e.g.: going back and forth from another page), I see a network request to the endpoint set in eventsQueryOptions request. How can I avoid that, and only load after a set period (or after invalidation)?
like-gold
like-gold•5mo ago
stale time on query ?
wise-white
wise-whiteOP•5mo ago
Yeah was missing that! You've been great help Manuel! One last thing: when I create/update an event and then come back to the events page, there is a small time between the old list showing and the updated list showing. It's not a great deal, but is there a way to avoid this state change being visible to the user?
like-gold
like-gold•5mo ago
usually you want this kind of stale-while-revalidate (SWR) behavior as it allows faster navigations at the expense of briefly showing outdated data one way could be to remove the query from the cache instead of just invalidating it https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientremovequeries cc @TkDodo 🔮 is there a better way?
eastern-cyan
eastern-cyan•5mo ago
await queryClient.fetchQuery in the route loader

Did you find this page helpful?