swr icon indicating copy to clipboard operation
swr copied to clipboard

`optimisticData` function does not use fallback as `currentData`

Open djfarly opened this issue 1 year ago • 4 comments

Bug report

When using the optimisticData function (see #1850) does not use data from SSR fallback as currentData. As long as SWR has not revalidated the fallback data you can't really use it as basis for new optimisticData.

Description / Observed Behavior

consider this example

const myListResponse = useSWR(`api/myList`);

async function addToList(item) {
  await myListResponse.mutate(
    async () => { /* some fetch PATCH adding the item to the list */ },
    {
        populateCache: true,
        rollbackOnError: true,
        revalidate: false,
        optimisticData(currentData?) {
          return [...(currentData ?? []), item];
        },
      }
  )
}

Even if fallback: { "api/myList": ["foo", "bar"] } is set in <SWRConfig> the currentData argument is undefined when addToList() is called before the fallback has been revalidated.

This leads to undesired results:

  • data is ['foo', 'bar'] initially (from fallback)
  • addToList('baz') is called
  • data is now ['baz'] (from optimisticData function) because currentData was undefined but should have been ['foo', 'bar']
  • fetched data arrives
  • data is now ['foo', 'bar', 'baz']

In my case addToList it is called from an effect but it could also be caused by a fast clicking user or slow network connection.

Expected Behavior

currentData should use fallback data if it can.

And the process should be like this:

  • data is ['foo', 'bar'] initially (from fallback)
  • addToList('baz') is called
  • data is now ['foo', 'bar', 'baz'] (from optimisticData function) because currentData was ['foo', 'bar']
  • fetched data arrives
  • data still is ['foo', 'bar', 'baz']

Repro Steps / Code Example

See snippet above - I'm not sure how to best create a repro without building a sample api. 🙈

Additional Context

SWR version. 2.0.0-beta.6 in next 12.2

I have found two possible workarounds.

  1. don't mutate if still loading:
async function addToList(item) {
  if (myListResponse.isLoading) return;

  await myListResponse.mutate(
  …
  1. fallback to fallback data manually:
        optimisticData(currentData?) {
          currentData = currentData ?? myListResponse.data;

          return [...(currentData ?? []), item];
        },

Both seem suboptimal.

Thanks for investigating this. ❤️

djfarly avatar Aug 11 '22 11:08 djfarly

Thanks for reporting this! Sounds like a fair case to cover 👍

huozhi avatar Aug 11 '22 12:08 huozhi

After dicussing with @shuding , it's more like by designed currently. since the fallbackData is per hook, you can have different fallback value for the same resource. But mutate() works per resource, so you might not be able to know the fallback value for each hook.

huozhi avatar Nov 14 '22 19:11 huozhi

@huozhi I'm facing the same problem as @djfarly at the moment. The documentation indeed states that fallbackData is per hook, but to my understanding fallback is not. Still the data stored as fallback is not being use as currentData. Is this by design as well?

peerjollux avatar Nov 25 '22 08:11 peerjollux

Currently we're not using it to commit the data into the cache, the fallback and the fallbackData are all for fallback purpose when data is undefined. Even though fallback is set through SWR configuration, it doesn't fill data into cache.

So in your case I think you'd more like to use mutate to fill the cache first, or just share the fallback value between the mutation fetcher and swr configuration

huozhi avatar Nov 25 '22 16:11 huozhi