Queries read; mutations change data on the server. useMutation wraps a function that sends the change and tracks its state (isPending, isError, isSuccess), but unlike a query it runs only when you call mutate(), is never cached under a key, and is not retried by default. BookNest's heart button saves a book for later:
function useFavorite(id: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: () => toggleFavorite(id),
// ...
onSettled: () => qc.invalidateQueries({ queryKey: keys.book(id) }), // refetch the truth
});
}The button calls favorite.mutate(). Without the last option, the cached book would still say favorite: false after the server had changed it. invalidateQueries marks every query whose key starts with ['book', 3] as stale and refetches the ones on screen. The server's log for one tap:
16:16:21 POST /books/3/favorite 200 16:16:22 GET /books/3 200
Invalidation matches by prefix, so invalidateQueries({ queryKey: ['books'] }) would refresh every page of the catalog at once. When the response already contains the new state, as it does here ({ id, favorite }), you can skip the round trip and write it into the cache with qc.setQueryData() in onSuccess. onSettled runs after success or failure, which makes it the safe place to resynchronize.