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

Optimistic local mutations without setting dedupingInterval to 0 #152

Open
davidblnc opened this issue Mar 25, 2021 · 6 comments
Open

Optimistic local mutations without setting dedupingInterval to 0 #152

davidblnc opened this issue Mar 25, 2021 · 6 comments
Labels
enhancement New feature or request swr

Comments

@davidblnc
Copy link

Hi! I was looking for alternatives to SWR in Vue and I found this library, which is great!

I found how to do optimistic local mutations in this issue (#83), but I'm kind of worried about having to set dedupingInterval to 0, because it means we can't really use deduping intervals and optimistic mutations at the same time.

Looking through the code, I think putting these lines inside an if could work.

Is there a way to do this I haven't found? Otherwise, would you be willing to accept a PR to add this functionality?

@darrenjennings
Copy link
Contributor

@davidblnc sounds good, we should add the shouldRevalidate argument to mutate with the default to true, then you can send false without setting dedupingInterval to 0

https://github.com/vercel/swr/blob/master/src/types.ts#L68-L72
https://github.com/vercel/swr/blob/master/src/use-swr.ts#L102

@darrenjennings darrenjennings added enhancement New feature or request swr labels Mar 25, 2021
@davidblnc
Copy link
Author

Unfortunately I haven't been able to get the tests to pass.
I attach the test code in case someone can give a few pointers towards the solution.

it('mutate respects "shouldRevalidate" option', async done => {
  const loadData = key => new Promise(res => setTimeout(() => res(key), 100))
  let firstFetch = true;
  const vm = new Vue(defineComponent({
    template: `<div>hello, {{data}}</div>`,
    setup() {
      const { data, mutate: revalidate } = useSWRV("mutate-should-not-revalidate", () => {
        if (firstFetch) {
          firstFetch = false
          return 'initial-state'
        }
        return 'revalidated-state'
      });

      setTimeout(() => {
        mutate("mutate-should-not-revalidate", loadData('optimistic-update-state'), undefined, undefined, false);
      }, 100);

      setTimeout(() => {
        revalidate()
      }, 300);

      return { data };
    }
  })).$mount();

  expect(vm.$el.textContent).toBe('hello, initial-state')

  timeout(200)
  await tick(2)

  expect(vm.$el.textContent).toBe('hello, optimistic-update-state')

  timeout(100)
  await tick(2)

  expect(vm.$el.textContent).toBe('hello, revalidated-state')

  done()
})

@darrenjennings
Copy link
Contributor

@davidblnc this test passes for me on master (since i don't have any code that has shouldRevalidate argument). It should fail once you implement the shouldRevalidate functionality and revalidated-state should not happen. Maybe open a pull-request and we can chat there. Also, I think maybe we should move the mutate signature to be

type MutatorOptions = {
  shouldRevalidate: boolean
  cache: SWRVCache<Omit<IResponse, 'mutate'>>
  ttl: number
}
export type Mutator<Data = any> = (
  key: IKey,
  data?: Data | Promise<Data>,
  options?: MutatorOptions
) => Promise<Data | undefined>

const mutate: Mutator = async (key, res, options) => {
  // ...
}

// e.g.
mutate("mutate-should-not-revalidate", loadData('optimistic-update-state'), {
  shouldRevalidate: false
})

which will give us the ability to fallback to default cache and ttls.

@davidblnc
Copy link
Author

After testing a bit, I think the library actually doesn't revalidate after calling mutate.

In this example, I would've expected to see:

  1. First the initial state (initial)
  2. Then the optimistic state (optimistic)
  3. Finally, mutate calls the fetcher again to revalidate and it prints "initial"

@darrenjennings
Copy link
Contributor

@davidblnc whoops you're right... the mutate actually needs to be added as a feature #92 (comment)

@derpdead
Copy link

Hello guys, for those who are looking for workaround I've implemented tracking mechanism for mutate. It's not elegant way.

import useSWRV from 'swrv'
import { IConfig, IKey, fetcherFn } from 'swrv/dist/types'

const cacheInstances = new Map()
const cache = new Map()

export const mutateSWRCache = (key: string) => {
  const mutate = cache.get(key)

  if (mutate) {
    mutate()
  }
}

export const mutateSWRCaches = (keys: Array<string>) => {
  for (const key of keys) {
    const mutate = cache.get(key)

    if (mutate) {
      mutate()
    }
  }
}

export default <T>(key: IKey, fn: fetcherFn<T> | undefined | null, config?: IConfig) => {
  const keyRef = computed(() => (typeof key === 'function' ? key() : key))
  const swr = useSWRV<T>(key, fn, config)

  watch(
    keyRef,
    () => {
      if (!cache.has(keyRef.value)) {
        cache.set(keyRef.value, swr.mutate)
        cacheInstances.set(keyRef.value, 1)
      } else {
        cacheInstances.set(keyRef.value, cacheInstances.get(keyRef.value) + 1)
      }
    },
    {
      immediate: true,
    }
  )

  onScopeDispose(() => {
    if (cacheInstances.get(keyRef.value) === 1) {
      cache.delete(keyRef.value)
      cacheInstances.delete(keyRef.value)
    } else {
      cacheInstances.set(keyRef.value, cacheInstances.get(keyRef.value) - 1)
    }
  })
  return swr
}

Then u can use it as:

mutateSWRCaches(['clients', 'product'])
mutateSWRCache('clients')

And to make it work instead of useSWRV directly, use

export default () =>
  useMutateFetch<Array<ClientType>>(
    'clients',
    () => $fetch('/clients', { method: 'GET' }),
    IMMUTABLE_CONFIG
  )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request swr
Projects
None yet
Development

No branches or pull requests

3 participants