Mutations with RTK Query

A mutation writes to the server. build.mutation describes the request, and the generated Hook returns a trigger function plus a result object — it never fires on render. What happens afterwards is the interesting part: invalidatesTags tells RTK Query which cached queries are now wrong, and every mounted component subscribed to them refetches on its own.

A mutation, tag invalidation, and an optimistic updateJSX
addProduct: build.mutation({
  query: (body) => ({ url: 'products', method: 'POST', body }),
  invalidatesTags: [{ type: 'Product', id: 'LIST' }],   // refetches the list query
}),
renameProduct: build.mutation({
  query: ({ id, name }) => ({ url: `products/${id}`, method: 'PATCH', body: { name } }),
  async onQueryStarted({ id, name }, { dispatch, queryFulfilled }) {
    const patch = dispatch(catalogApi.util.updateQueryData('getProducts', 1, draft => {
      const item = draft.find(p => p.id === id);
      if (item) item.name = name;                       // applied before the server replies
    }));
    try { await queryFulfilled; } catch { patch.undo(); }
  },
}),
function AddProduct() {
  const [addProduct, { isLoading, error }] = useAddProductMutation();
  const submit = (e) => { e.preventDefault(); addProduct({ name: e.target.name.value }); };
  return <form onSubmit={submit}>
    <input name="name" /><button disabled={isLoading}>Add</button>
    {error && <p role="alert">Could not save</p>}</form>;
}

Tags are the whole invalidation model. A query declares what it holds with providesTags; a mutation declares what it breaks with invalidatesTags. Invalidating { type: 'Product', id: 'LIST' } — the conventional sentinel for "the collection" — refetches the list without touching cached detail entries, while { type: 'Product', id: 7 } refetches only that product. Both accept a function of (result, error, arg), so a failed mutation invalidates nothing.

onQueryStarted runs when the mutation begins and gives you the optimistic path: util.updateQueryData writes into a cached query through Immer 28,983 and returns a patch with .undo(), so a rejected request rolls the UI back. Use it where the outcome is nearly certain — a rename, a toggle, a like — and stay with tag invalidation elsewhere; a wrong optimistic update is worse than a slow one.