Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mutate() and the deduping interval #1417

Open
hazae41 opened this issue Aug 31, 2021 · 15 comments
Open

mutate() and the deduping interval #1417

hazae41 opened this issue Aug 31, 2021 · 15 comments

Comments

@hazae41
Copy link

hazae41 commented Aug 31, 2021

Bug report

Description / Observed Behavior

I prefetch and mutate some data when the user hovers a link using mutate(key, data, false)
When the user clicks the link, the page is loaded and useSWR revalidates the data.

However, the hover and the click where made within deduping interval.

Expected Behavior

Do not revalidate if mutate(key, data, false) has been called within the deduping interval

Repro Steps / Code Example

export async function prefetch(){
  mutate("/api/data", () => fetcher("/api/data"), false)
}

export function DataPage(){
  const res = useSWR("/api/data", fetcher) // will revalidate even though mutate has been called withing the deduping interval

  return <>
    ...
  </>
}

Additional Context

[email protected]

@promer94
Copy link
Collaborator

could you try useSWR("/api/data", fetcher, { revalidateIfStale: false })

@hazae41
Copy link
Author

hazae41 commented Aug 31, 2021

I tried and it doesn't revalidate, but I want it to revalidate if the stale data is older than the deduping interval

@promer94
Copy link
Collaborator

promer94 commented Aug 31, 2021

dedupingInterval is the time between two revalidate not the time between mutate and revalidate.

------------dedupingInterval----------
------mutate---revalidate----revalidate--                              second one will be ignored
---mutate-----revalidate---                                will still revalidate 

if you want to send less requests, revalidateIfStale could save one request when hook mounted.

@hazae41
Copy link
Author

hazae41 commented Sep 1, 2021

There should be a time between mutate a revalidate then

@computerjazz
Copy link

computerjazz commented Sep 1, 2021

I have this same issue. Here's my use case:

I'm on React Native and am wrapping useSwr in a custom hook, let's call it useSomeData({ paused: boolean }). I want to be able to do a "soft" revalidation when various conditions are met: when I "unpause" useSomeData, when some user state changes, etc. However, I want these revalidations to be deduped, and I don't want each instance of my useSomeData() hook to make a separate request, as would be the case if I called mutate in an effect.

fwiw I was able to solve this using undocumented dedupe param in revalidate argument in the pre-1.0.0 release: https://github.com/vercel/swr/blob/0.5.6/src/use-swr.ts#L360

so this does what I want it to do:

function useSomeData({ paused }) {
  const readData = useSwr("some/key", myFetcher, { isPaused: () => paused })
  const { revalidate, mutate } = readData

  useEffect(() => {
      // @ts-ignore dedupe is a revalidation option but not included on the type: https://github.com/vercel/swr/blob/0.5.6/src/use-swr.ts#L360
    if (!paused) revalidate({ dedupe: true })

    // note that calling 'mutate' here would make an api request for _each instance_ consuming this hook. I don't want that.
    // mutate()
 }, [paused, revalidate])

}

However, since revalidate was removed in 1.0.0 I'm going to need to find a new path forward.

@promer94
Copy link
Collaborator

promer94 commented Sep 3, 2021

Each instance of my useSomeData() hook to make a separate request, as would be the case if I called mutate in an effect

This doesn't sound correct to me.
If multiple hooks mounted, calling mutate will only trigger one revalidate.
FYI: https://stackblitz.com/edit/nextjs-ybma55?file=pages%2Findex.js @computerjazz

@shuding
Copy link
Member

shuding commented Sep 3, 2021

BTW from your code snippet, I suppose the new hook in #1262 is what you need. @computerjazz

@computerjazz
Copy link

computerjazz commented Sep 3, 2021

Thanks @promer94 and @shuding !

So, here's a more complete example of my use case: https://stackblitz.com/edit/nextjs-4xtgyg?file=pages%2Findex.js

Notice that if the fetch starts as paused: true, and then toggles to paused: false at some time later, a fetch does not occur by default, so we have to trigger it manually. Adding the useEffect and calling mutate results in each instance of the hook making its own separate fetch.

The new hook in #1262 looks like it may solve the issue as long as it deduplicates correctly, but I wonder if this specific case should be handled within the useSwr hook itself in an effect that calls revalidate({ dedupe: true }) when config.isPaused() state becomes false?

And apologies if I'm steering this issue too far away from the original report. I'd be happy to open a new issue and move discussion there.

@swushi
Copy link

swushi commented Aug 19, 2022

I also feel that it makes sense to dedupe all mutate calls.

It feels like a pretty common pattern to ingest the same hook in multiple components, and allow SWR to handle the de-duping to avoid unnecessary re-renders. In React-Native, you have to handle revalidateOnFocus on your own, something like the following:

const useProfile = () => {
  const swr = useSwr('/users/me');
  useFocusEffect(() => swr.mutate());
  return swr;
}

const Profile = () => {
  const { data } = useProfile();
  return <ProfileHeader />
}

const ProfileHeader = () => {
  const { data } = useProfile();
  return <Foo />
}

Standard fetching is deduped correctly, and that's the biggest selling point of SWR for me. However, with this custom revalidation on focus for react-native, every call to mutate, which would be every component that uses useProfile, another request would be sent out.

In this example, two requests are being sent out, when I would expect them to be deduped. I don't really think it has anything to do with paused property.

@hazae41
Copy link
Author

hazae41 commented Aug 24, 2022

For those who don't want to scratch their heads anymore, wondering why the fuck swr does this, I made my own library.

https://github.com/hazae41/xswr

It uses composition-based hooks and has a very easy learning curve, and 0% weird behaviour.

@siawyoung
Copy link

I thought I was going mad, but turns out this is actually a thing. Why aren't mutate calls deduped too?!

@zongyz
Copy link

zongyz commented Mar 4, 2023

I also have a similar problem, in the case of revalidateOnFocus=true, when using useSWR("/list") to provide list data for a page, if there is a refresh button on the page, click the button to use mutate( "/list") Refresh list data, when switching to another window, and then click the refresh button to return to the page, re-focus and button click will initiate two requests for "/list"

@Lwdthe1
Copy link

Lwdthe1 commented May 28, 2023

I came here searching to make sure that mutate() is independent and will always fulfill the desire to trigger a unique request to the server regardless of dedupingInterval. I'm glad to see that's exactly how it works because if I (from my user's action) explicitly call mutate, I want it to do exactly that without exception.

Perhaps a middle ground is to have mutate() or useSWR() accept a configuration that allows for what the others are asking, so that they can make mutate() dependent on / respect dedupingInterval.

@sannajammeh
Copy link

I am also trying to make mutate actually dedupe the requests. Is it possible?

I have a revalidateOnFocus middleware for react-native which currently causes 60 requests as its used in a list of 60 components. The default behavor works as useSWR only is called once as the key is the same, but trying to mutate the hooks based on focus will not dedupe.

@sannajammeh
Copy link

sannajammeh commented Aug 30, 2023

I am also trying to make mutate actually dedupe the requests. Is it possible?

I have a revalidateOnFocus middleware for react-native which currently causes 60 requests as its used in a list of 60 components. The default behavor works as useSWR only is called once as the key is the same, but trying to mutate the hooks based on focus will not dedupe.

Honestly, pretty easy solution with swr middleware

const dedupeCache = new Map();

export const revalidateOnFocus: Middleware = (useSWRNext) => {
  return (key, fn, config) => {
    const swr = useSWRNext(key, fn, config);
    useFocusEffect(
      useCallback(() => {
        if (typeof swr.data === "undefined") return;

        const serializedKey = unstable_serialize(key);
        if (dedupeCache.has(serializedKey)) return;
        dedupeCache.set(serializedKey, true);
        swr.mutate().finally(() => {
          dedupeCache.delete(serializedKey);
        });
      }, [swr.data, swr.mutate]),
    );

    return swr;
  };
};

// Your query
const {data} = useSWR("/path", {
   use: [revalidateOnFocus]
})

// Or global
<SWRConfig value={{ use: [revalidateOnFocus] }}>
{children}
</SWRConfig>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants