TanStack Query (React Query): Handling HTTP Requests with EaseEntry 7 of 10

Invalidating Queries

queryClient.invalidateQueries() marks cached queries as stale and refetches the ones currently on screen, so the UI shows the latest data after a mutation.

Notes

  • After a POST/PUT/DELETE, any query showing that data is outdated. Invalidate it in onSuccess (or onSettled).
  • Prefix matching: invalidateQueries({ queryKey: ['events'] }) matches every key starting with 'events': ['events'], ['events', { max: 3 }], ['events', { search: 'x' }], ['events', '123'].
  • exact: true: only the key that matches exactly (['events']), not the dynamic variants.
  • Target one item: invalidateQueries({ queryKey: ['events', id] }).
  • refetchType:
ValueBehavior
'active' (default)Mark stale; refetch queries used by mounted components
'all'Mark stale; refetch every matching query, mounted or not
'none'Mark stale only; refetch later, when next used
  • Use refetchType: 'none' when an immediate refetch would fail or waste a request. Course example: after deleting an event, refetching that event's detail query (still mounted while navigating away) would return 404.
  • queryClient is imported from its module (Entry 1), so it works in components, mutations, and router loaders. Inside components you can also use useQueryClient().

What / Why / When

What

Cache invalidation after writes.

Why

Keeps all screens consistent with the backend without manual refetch calls.

When

In every mutation that changes data some query displays.

Code compare

React
// All event lists and detailsqueryClient.invalidateQueries({ queryKey: ["events"] });​// Only the plain ['events'] queryqueryClient.invalidateQueries({ queryKey: ["events"], exact: true });​// One event's detailqueryClient.invalidateQueries({ queryKey: ["events", id] });
React
// pages/EventDetails.tsx — delete without refetching the deleted eventconst { mutate: deleteMutate, isPending: isDeleting } = useMutation({  mutationFn: deleteEvent,  onSuccess: () => {    queryClient.invalidateQueries({ queryKey: ["events"], refetchType: "none" });    navigate("/events");  },});​function handleDelete() {  deleteMutate({ id: id! });}

A version signal in a root service plays the role of invalidation: resources that read it reload whenever it changes.

Angular
// services/events.service.ts (addition)private _version = signal(0);readonly version = this._version.asReadonly();​invalidate() {  this._version.update((v) => v + 1);}
Angular
// Any component that should refresh after mutationsprotected events = rxResource({  params: () => ({ max: 3, version: this.eventsService.version() }),  stream: ({ params }) => this.eventsService.getEvents({ max: params.max }),});
Angular
// After a mutationthis.eventsService.createEvent(formData).subscribe(() => {  this.eventsService.invalidate(); // every resource reading version() reloads  this.router.navigate(["/events"]);});

For a single component, call resource.reload() directly.

Differences and gotchas

  • TanStack Query invalidation is key-based and global. The Angular version signal is coarse (everything reading it reloads); use one signal per data type for finer control.
  • The notes compare invalidation to switchMap. It's closer to emitting a "refresh" trigger that queries listen to, which is exactly what the version signal does.
  • Angular has no refetchType: 'none'. Navigating away first destroys the component and its resource, so the deleted item is never refetched.

Quick reference

TaskReactAngular
Refresh all relatedinvalidateQueries({ queryKey: ['events'] })eventsService.invalidate()
Exact key onlyexact: trueSeparate version signal
One itemqueryKey: ['events', id]resource.reload()
Mark stale onlyrefetchType: 'none'— (component destroyed on navigate)
Access clientImported queryClient / useQueryClient()inject(EventsService)