swr icon indicating copy to clipboard operation
swr copied to clipboard

Bound/global mutate functions do not mutate remote data

Open TrevorBurnham opened this issue 1 month ago • 2 comments

Bug report

I'm a longtime SWR user, but I'm still puzzled by the behavior of the three different mutate functions:

  1. The bound mutate function returned by useSWR.
  2. The global mutate function accessible via useSWRConfig or with import { mutate } from "swr".
  3. The mutation trigger function returned by useSWRMutation.

The docs seem to describe these three functions as three ways to achieve the same goal: mutating data. When I hear "mutating data" in the context of a framework like SWR, I assume that the goal is to update data both remotely and locally, either via an optimistic update (update the local data immediately and revert it if the remote update fails) or a pessimistic update (wait to update the local data until the remote update is confirmed).

However, in practice it seems that only useSWRMutation performs remote updates. The other two mutation functions only affect local state, which creates an inconsistency with the remote state.

That means that you get some very strange behavior by default: If you use the bound or global mutate function to perform an update, with no additional parameters, then that update will be reverted moments later when revalidation occurs.

Description / Observed Behavior

Let's look at an example (a simplified version of the CodeSandbox below):

const { data, mutate } = useSWR<{ count: number }>("/count");

return (
  <div>
    <span>Count: {data?.count ?? ""}</span>
    <button onClick={(() => {
      mutate({ count: data.count + 1 });
    }}>
      Increment
    </button>
  </div>
)

This looks like a straightforward use of the bound mutate function. However, if you click the button, the count will go up by 1, then back down to its original value when revalidation occurs, because the remote state is not modified.

Expected Behavior

I'd expect the bound and global mutate functions to trigger the fetcher to modify the remote state, like useSWRMutation does. The optimistic updates example in the docs seems to show the global mutate function being used in this way, but I've never been able to get it to work; is there something I'm missing?

Alternatively, I'd expect the docs to be very clear that the bound and global mutate functions are only useful when you're making the remote update separately, and that useSWRMutation is the preferred way of performing mutations.

Repro Steps / Code Example

This CodeSandbox demonstrates how the three mutation functions behave in a very simple scenario: https://codesandbox.io/p/devbox/focused-euler-sp668n?file=%2Fapp%2Fpage.tsx%3A20%2C14

Additional Context

SWR version: 2.2.5

TrevorBurnham avatar May 24 '24 21:05 TrevorBurnham