useQClient
useQClient is the imperative escape hatch. It returns a typed wrapper around the underlying QueryClient so you can invalidate, prefetch, and edit cache entries by route name (with full type inference).
import { useQClient } from "@/api/client";
const qc = useQClient();qc.invalidateTag({ type: "post", facilityId: "f1" });The returned object is stable across renders — it’s safe to put in dependency arrays.
interface QClient<TSchema extends Schema> { invalidateTag(tag: TagInput<TSchema>): Promise<void>; invalidate<TRoute extends RouteKey<TSchema>>( route: TRoute, params?: PartialRouteParams<TSchema, TRoute>, ): Promise<void>; invalidateAll(): Promise<void>; setData<TRoute extends ReadableRouteKey<TSchema>>( route: TRoute, params: RouteParams<TSchema, TRoute>, data: RouteResponse<TSchema, TRoute>, ): void; updateData<TRoute extends ReadableRouteKey<TSchema>>( route: TRoute, params: RouteParams<TSchema, TRoute>, updater: ( prev: RouteResponse<TSchema, TRoute> | undefined, ) => RouteResponse<TSchema, TRoute>, ): void; prefetch<TRoute extends ReadableRouteKey<TSchema>>( route: TRoute, params: RouteParams<TSchema, TRoute>, options?: { staleTime?: number }, ): Promise<void>;}invalidateTag
Section titled “invalidateTag”Invalidate every cached query whose tags (in the schema) match the given tag input. This is the same algorithm useM uses for invalidatesTags:
qc.invalidateTag({ type: "post", facilityId: "f1" });// Invalidates every "post"-tagged query for facility "f1".
qc.invalidateTag({ type: "post" });// Invalidates every "post"-tagged query, regardless of facilityId.Use this after a non-useM side effect (e.g. an out-of-band server-sent event):
useEffect(() => { const es = new EventSource("/events"); es.addEventListener("post-changed", (msg) => { const { facilityId } = JSON.parse(msg.data); void qc.invalidateTag({ type: "post", facilityId }); }); return () => es.close();}, [qc]);invalidate(route, params?) — prefix vs exact
Section titled “invalidate(route, params?) — prefix vs exact”Invalidate by route name. The params argument is partial: omit it for a route-wide invalidation, supply it for an exact match.
// Exact — only this queryqc.invalidate("getPost", { pathParams: { facilityId: "f1", postId: "p1" } });
// Prefix — every getPost call, any postIdqc.invalidate("getPost", { pathParams: { facilityId: "f1" } });
// Prefix — every call to this routeqc.invalidate("listPosts");Under the hood, this uses TanStack Query’s exact: false filter against the query key prefix ["api", METHOD, resolvedPath, ...]. See Query keys.
invalidateAll
Section titled “invalidateAll”Nuke the whole cache. Useful after a logout or a tenant switch:
function logout() { tokenStore.clear(); void qc.invalidateAll(); router.push("/login");}setData — replace a cache entry
Section titled “setData — replace a cache entry”Synchronously write a fully-typed value into the cache for a given route + params:
qc.setData( "getPost", { pathParams: { facilityId: "f1", postId: "p1" } }, { id: "p1", facilityId: "f1", title: "Edited", body: "…", createdAt: "…" },);This is also how you hydrate from a loader/server payload — see SSR & loaders.
updateData — patch a cache entry
Section titled “updateData — patch a cache entry”updateData is the functional cousin: instead of providing the whole next value, you provide an updater.
qc.updateData( "getPost", { pathParams: { facilityId: "f1", postId: "p1" } }, (prev) => (prev ? { ...prev, title: "Edited" } : prev),);Common after a non-list response patches one item but the list cache is also stale.
prefetch
Section titled “prefetch”Warm the cache for a route, typically on hover or route preload:
function PostLink({ post, facilityId }: { post: Post; facilityId: string }) { const qc = useQClient(); return ( <a href={`/posts/${post.id}`} onMouseEnter={() => void qc.prefetch( "getPost", { pathParams: { facilityId, postId: post.id } }, { staleTime: 30_000 }, ) } > {post.title} </a> );}staleTime controls how long the prefetched data stays fresh — pass a large value if you’re prefetching way ahead of navigation.
Imperative use outside a component
Section titled “Imperative use outside a component”If you need cache control outside React (e.g. a global logout function), reach for api.queryClient directly and skip useQClient:
import { api } from "@/api/client";
export function logout() { void api.queryClient.invalidateQueries(); api.queryClient.clear();}The queryClient from createApiClient is the same instance backing every hook in your app.