T
TanStack2y ago
conscious-sapphire

Invalidate loader data

How can I invalidate the loader data after certain mutation. I tried using queryClient.invalidateQueries and it doesn't seem to work Here is my loader
export const Route = new FileRoute(
"/_applications/applications/$applicationId/choose-program"
).createRoute({
component: Component,
loader: ({ context, params: { applicationId } }) =>
context.queryClient.ensureQueryData(
queryOptions({
queryKey: ["applications", { applicationId }],
queryFn: () => fetchApplication(applicationId),
})
),
});
export const Route = new FileRoute(
"/_applications/applications/$applicationId/choose-program"
).createRoute({
component: Component,
loader: ({ context, params: { applicationId } }) =>
context.queryClient.ensureQueryData(
queryOptions({
queryKey: ["applications", { applicationId }],
queryFn: () => fetchApplication(applicationId),
})
),
});
And here is my mutation
const application = Route.useLoaderData({ select: (result) => result.data });

const updateMutation = useMutation({
mutationFn: ...
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["applications", { applicationId }],
});
},
});
const application = Route.useLoaderData({ select: (result) => result.data });

const updateMutation = useMutation({
mutationFn: ...
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["applications", { applicationId }],
});
},
});
6 Replies
fair-rose
fair-rose2y ago
Don’t use the loader data, just call useQuery()
conscious-sapphire
conscious-sapphireOP2y ago
sure, is there a particular reason?
tame-yellow
tame-yellow2y ago
Because your loader isn't tied to Tanstack Query, as the function is simply warming up the Query cache using the set queryOptions. Invalidating the Query cache for that key doesn't trigger a reload of the application, just marks the data in Tanstack Query as stale. As such, your app then goes into a state where your data stored in the Tanstack Query cache is being invalidated whilst there hasn't been a navigation or such signal to re-run the loader for the route. When it comes to your data flow into the application, consider the route loader function as simply a way to perform/ensure some queries have their data available when navigating to certain routes. Then consume your data only from the Tanstack Query cache. Treat it as your source of truth. ... and if you want to further fine-tune the behaviours as to how your data is being cached in Tanstack Query, it'd be worth while checking out its docs.
conscious-sapphire
conscious-sapphireOP2y ago
I see, thanks for the explanation. I’ve been using the useQuery hook all along and never had an issue. After switching to the tanstack router I decided to give a try to fetch data from the loader function and ran into this issue. Now I understand why it’s happening
solid-orange
solid-orange2y ago
@Sean Cassiere Just to add an additional option, couldn’t he had the query client to context, and fetch in the loader? Then invalidate that query when a mutation occurs? Or invalidate the path with the router? I don’t think there is any reason to do it this way because query works great but it seems if you want to take advantage of the loader or before load, query needs to be setup different
tame-yellow
tame-yellow2y ago
Just to add an additional option, couldn’t he had the query client to context, and fetch in the loader? Then invalidate that query when a mutation occurs?
Yup, and that is what is happening according to the setup that was shown in the original post. The recommendation was to subscribe/consume the data being fetched in the loader function via the useQuery hook instead of the useLoaderData hook.
Or invalidate the path with the router? I don’t think there is any reason to do it this way because query works great but it seems if you want to take advantage of the loader or before load, query needs to be setup different
Router path invalidation would only call the beforeLoad and loader functions, without actually asking Query to perform a refetch. This is because Query may not actually perform a refetch since it is using its own internals for monitoring if this query-hash is stale, and should be revalidated. But you absolutely right in the fact that the beforeLoad and loader functions could be optimized for this workflow. This is how I'd do it. Ignore the file-based routing, the same concepts can be carried over to the manual setup.
// $applicationId.route.tsx
export const Route = new FileRoute('/appliciations/$applicationId').createRoute({
beforeLoad: ({ params }) => ({
getApplicationByIdOptions: queryOptions({
queryKey: ["applications", params.applicationId],
...
})
})
})


// $application.loader.tsx
export const loader = FileRouteLoader('/applications/$applicationId')(async ({ context }) => {
context.queryClient.ensureQueryData(context.getApplicationByIdOptions)
})


// $aplicationId.component.tsx
const api = new RouteApi({ id: '/applications/$applicationId' })

export const component = function Component() {
const { getApplicationByIdOptions } = api.useRouteContext();
const queryClient = useQueryClient();

// the next two lines are only required if you are using this application data for something other than the mutation below
const query = useSuspenseQuery(getApplicationByIdOptions)
const application = query.data;

const mutate = useMutation({
...,
onSuccess: () => queryClient.invalidateQueries({ queryKey: getApplicationByIdOptions.queryKey })
})
...
}
// $applicationId.route.tsx
export const Route = new FileRoute('/appliciations/$applicationId').createRoute({
beforeLoad: ({ params }) => ({
getApplicationByIdOptions: queryOptions({
queryKey: ["applications", params.applicationId],
...
})
})
})


// $application.loader.tsx
export const loader = FileRouteLoader('/applications/$applicationId')(async ({ context }) => {
context.queryClient.ensureQueryData(context.getApplicationByIdOptions)
})


// $aplicationId.component.tsx
const api = new RouteApi({ id: '/applications/$applicationId' })

export const component = function Component() {
const { getApplicationByIdOptions } = api.useRouteContext();
const queryClient = useQueryClient();

// the next two lines are only required if you are using this application data for something other than the mutation below
const query = useSuspenseQuery(getApplicationByIdOptions)
const application = query.data;

const mutate = useMutation({
...,
onSuccess: () => queryClient.invalidateQueries({ queryKey: getApplicationByIdOptions.queryKey })
})
...
}

Did you find this page helpful?