Mutations with useMutation

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:

src/app/book/[id].tsx: the favorite mutation (excerpt)JavaScript
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:

Output of 163
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.