`optimisticData(old)` in `useSWRMutation` is not invoked with latest data from cache
Bug report
Description / Observed Behavior
When calling the trigger function returned from useSWRMutation in rapid succession(i.e. before the fetcher has returned) and using the optimisticData callback, the callback does not receive the latest data from the cache, or perhaps more importantly: it does not receive the data that was returned from its last invokation if there have been such and the fetcher is still unresolved.
Expected Behavior
I expect the data sent to optimisticData to always be the latest (optimistic) data
Repro Steps / Code Example
https://codesandbox.io/s/aged-shadow-l4wrhj?file=/src/App.js
Notice how the counter changes immediately to 2 but does not progress to 3 until the clicking stops and the fetcher is allowed to return
Additional Context
SWR version 2.1.1 (also in 2.2 in my project)
Not sure if you consider this a bug or a feature, considering the following text in the docs
Also, this hook doesn’t share states with other useSWRMutation hooks.
However, does this count as an "other hook"?
Possibly related to https://github.com/vercel/swr/issues/2186? I believe it's the same reasoning and both issues require proper enqueuing of mutate calls.
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
@JamieS1211 Can you provide an example of how you solved this problem ?
Is there an update on getting this fixed as it's a bit of a deal breaker for me using this in my project.
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
@JamieS1211 I tried this, and it's a good work around for what I'm after, thanks.
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
@JamieS1211 Can you provide an example of how you solved this problem ?
Sorry missed this - the documentation on bound mutation can be found here https://swr.vercel.app/docs/mutation.en-US#bound-mutate. Essentially use that, then when an optimistic update happens, the data variable from the hook updates and causes a re-render. Then you can do a mutation with the optimistic data present. In the example they show the mutation as: mutate({ ...data, name: newName }) however you can do as needed here.
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
@JamieS1211 Can you provide an example of how you solved this problem ?
Sorry missed this - the documentation on bound mutation can be found here swr.vercel.app/docs/mutation.en-US#bound-mutate. Essentially use that, then when an optimistic update happens, the
datavariable from the hook updates and causes a re-render. Then you can do a mutation with the optimistic data present. In the example they show the mutation as:mutate({ ...data, name: newName })however you can do as needed here.
So, if I am not mistaken, you essentially trigger a revalidation in between consecutive mutations?
Just worked around this myself by using a "boundMutate" and using the data form "useSWR" hook so I the data doesn't come from the function call on updating optimistic data.
@JamieS1211 Can you provide an example of how you solved this problem ?
Sorry missed this - the documentation on bound mutation can be found here swr.vercel.app/docs/mutation.en-US#bound-mutate. Essentially use that, then when an optimistic update happens, the
datavariable from the hook updates and causes a re-render. Then you can do a mutation with the optimistic data present. In the example they show the mutation as:mutate({ ...data, name: newName })however you can do as needed here.So, if I am not mistaken, you essentially trigger a revalidation in between consecutive mutations?
If I'm not mistaken this is what @JamieS1211 is doing as this is what worked for me:
const { data, mutate } = useSWR(['keyHere']);
function saveData(newData) {
mutate(/* Post here to server */,
{
populateCache: false,
optimisticData: () => {
// Notice using the data from the useSWR hook instead
// of the oldData that comes as an argument/parameter for this function
return [...data, newData];
}
);
}
Yep thats it, not sure if there is some drawback of this approach so maybe a maintainer could chip in on that front
For anyone else seeing old data passed to your optimisticData function, I had a slightly different issue. I was using a filter function for the keys (e.g. key => key.startsWith('blah')). When I instead did my filtering before starting the process, and called mutate for each key individually that seemed to fix it. So old was:
const { mutate } = useSWRConfig();
useEffect(() => {
setTimeout(onCompletelySeparateEventLoop, 1000);
function onCompletelySeparateEventLoop() {
const p = doAThing();
mutate(key => key.startsWith('blah'), doAThing, {
populateCache: false,
revalidate: false,
optimisticData: (cached) => transform(cached)
}
}
}, [])
New:
const { mutate, cache } = useSWRConfig();
useEffect(() => {
setTimeout(onCompletelySeparateEventLoop, 1000);
const keys = Array.from(cache.keys()).filter(key => key.startsWith('blah'));
function onCompletelySeparateEventLoop() {
const p = doAThing();
for (const key of keys) {
mutate(key, doAThing, {
populateCache: false,
revalidate: false,
optimisticData: (cached) => transform(cached)
}
}
}
}, [])
This still doesn't get the latest data if you have multiple optimisticData calls in a row - but cache.get(key) will have the latest in that case.