vercel / swr

React Hooks for Data Fetching
https://swr.vercel.app
MIT License
30.23k stars 1.21k forks source link

Global mutate only being called on immutable keys #2746

Open brandones opened 1 year ago

brandones commented 1 year ago

Bug report

Description / Observed Behavior

Calling mutate with a filter function key only runs that filter function on the key that uses useSWRImmutable.

Expected Behavior

I expect the filter function to be called on all existing SWR keys except the ones using useSWRImmutable.

Repro Steps / Code Example

I need to update and display orders. The display code is in a different microfrontend and different module from the update code.

The order display hook:

export function usePatientOrders(patientUuid: string, status: 'ACTIVE' | 'any') {
  const { careSettingUuid, drugOrderTypeUUID } = useConfig() as ConfigObject;
  const customRepresentation = 'bigLongStringOfStuff';
  const ordersUrl = `/ws/rest/v1/order?patient=${patientUuid}&careSetting=${careSettingUuid}&status=${status}&orderType=${drugOrderTypeUUID}&v=${customRepresentation}`;

  const { data, error, isLoading, isValidating } = useSWR<FetchResponse<PatientMedicationFetchResponse>, Error>(
    patientUuid ? ordersUrl : null,
    openmrsFetch,
  );

  const mutateOrders = useCallback(
    () => mutate((key) => typeof key === 'string' && key.startsWith(`/ws/rest/v1/order?patient=${patientUuid}`)),
    [patientUuid],
  );

  return { data, error, isLoading, isValidating, mutate: mutateOrders };
}

The order update hook (tried this with both useSWRConfig and with import { mutate } from "swr"):

export function useMutatePatientOrders(patientUuid: string) {
  const { mutate } = useSWRConfig()
  const mutateOrders = useCallback(
    () => mutate((key) => {
      const result = typeof key === 'string' && key.startsWith(`/ws/rest/v1/order?patient=${patientUuid}`);
      console.log(result, 'key', key)
      return typeof key === 'string' && key.startsWith(`/ws/rest/v1/order?patient=${patientUuid}`)
    }),
    [patientUuid],
  );

  return {
    mutate: mutateOrders,
  };
}

On calling that mutate function, the entire console log output is

false key /ws/rest/v1/systemsetting/visits.enabled?v=custom:(value)

or sometimes

false key /ws/rest/v1/systemsetting/visits.enabled?v=custom:(value) [api.ts:19:14](webpack://openmrs/esm-patient-orders-app/src/api/api.ts)
false key /ws/rest/v1/concept/?q=VITALS SIGNS&v=custom:(setMembers:(uuid,display,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units)) [api.ts:19:14](webpack://openmrs/esm-patient-orders-app/src/api/api.ts)

Indicating that the only call that has its key checked are these:

  const { data, isLoading, error } = useSWRImmutable<FetchResponse<{ value: 'true' | 'false' }>, Error>(
    `/ws/rest/v1/systemsetting/visits.enabled?v=custom:(value)`,
    openmrsFetch,
  );

and a similar one using useSWRImmutable for /ws/rest/v1/concept...

Here is the cache right before the POST is made and the update function is called:

Before: image

After: image

Additional Context

SWR 2.2.1.

App uses a microfrontends framework, so calls to SWR functions do not necessarily happen in the same React tree.

brandones commented 1 year ago

Ping, would love some support on this

daves28 commented 11 months ago

Running into a similar issue where the global mutate imported from 'swr' does not trigger a revalidation for hook instances that are using mutable keys, in my case an object. If I have a useSwr call with an object as a key, and then somewhere else call the global mutate with a copy of that object, there is no attempted revalidation in the hook.

Even if I create a memoized object and then pass that into both the hook and global mutate directly this behavior persists, so I don't think its a "same" object issue.

brandones commented 10 months ago

I never figured out why this issue was happening; why it was the case that mutate was only being called on immutable keys. The deeper problem, which my project resolved, was that the SWR cache was not being shared across the different microfrontends in my application. So if one microfrontend called mutate it would only run that on the useSWR calls that had been made within that microfrontend. Sharing the SWR cache across the application allows the mutate call to act on the useSWR calls from other parts of the application. The fact that mutate seemed to get called on the useSWRImmutable keys remains mysterious, but it is no longer my mystery.