johannschopplich / nuxt-api-party

🐬 Securely connect to any API with a server proxy and generated composables
https://nuxt-api-party.byjohann.dev
MIT License
260 stars 10 forks source link

`refresh()` is not forcing API call and the cached result is returned #68

Closed m-anwr closed 2 months ago

m-anwr commented 7 months ago

Environment

Working directory: /home/anwr/toys/darts_arena/frontend                                                              4:10:00 PM
Nuxt project info:                                                                                                   4:10:00 PM
------------------------------
- Operating System: Linux
- Node Version:     v18.16.0
- Nuxt Version:     3.11.1
- CLI Version:      3.11.1
- Nitro Version:    2.9.4
- Package Manager:  npm@9.5.1
- Builder:          -
- User Config:      ssr, runtimeConfig, devtools, css, modules, vue, imports, apiParty
- Runtime Modules:  @nuxtjs/tailwindcss@6.11.4, @vueuse/nuxt@10.9.0, @pinia/nuxt@0.4.11, nuxt-icon@0.4.2, nuxt-api-party@1.1.1
- Build Modules:    -
------------------------------

Reproduction

<template>
    <main class="mx-auto max-w-xl">
      <div v-if="pending" class="w-full py-4 flex justify-center items-center">
        <span class="px-2">Loading Posts</span>
        <span class="loading loading-spin"></span>
      </div>
      <button @click="refresh">Refresh</button>
      <PostList :posts="data" />
    </main>
  </div>
</template>

<script setup>
definePageMeta({
  layout: "main",
});
const { data, pending, refresh } = await useApiData("posts");
</script>

Describe the bug

When the Refresh button is clicked, the API is not called and the cached result is returned. Is that the expected behavior? I can't find any reference for that in the docs.

A workaround would be to use a custom cache key and change it if I wanted to refresh the content. otherwise, I have to set the cache to false

Additional context

No response

Logs

No response

mattmess1221 commented 7 months ago

I've also experienced this and have written a workaround. Just pass your AsyncData object to useUncachedData(). Using a custom key is required to ensure consistency.

import type { AsyncData } from "#app";

const CACHE_KEY_PREFIX = "$apiParty";

type Keys = MaybeRefOrGetter<string | string[]> | MaybeRefOrGetter<string>[];

/**
 * Modifies an `AsyncData` object's `refresh` method so it ignores the cache
 * when called.
 *
 * The `key` parameter must be the same as passed to the `key` option in
 * `useApiData()`.
 *
 * @param keyRef The cache key used in `useApiData()`
 * @param data The AsyncData result from `useApiData()`
 *
 * @example
 * ```ts
 * const { data, refresh } = useUncachedData(
 *   "myKey",
 *   useApiData("some/path", {
 *     key: "myKey"
 *   }),
 * )
 * ```
 */
export function useUncachedData<Data, Error>(keyRef: Keys, data: AsyncData<Data, Error>) {
  const keys = computed(() => {
    const keys = toValue(keyRef);
    if (Array.isArray(keys)) {
      return keys.map(useCacheRef);
    }
    return [useCacheRef(keys)];
  });

  // make refresh ignore the cache

  const { refresh } = data;

  const refreshInvalidate: typeof refresh = async (opts) => {
    // invalidate the cache
    for (const key of keys.value) {
      key.value = undefined;
    }
    return await refresh(opts);
  };

  data.refresh = refreshInvalidate;
  // awaiting gets the original asyncdata object, which is separate from the Promise
  data.then((asyncData) => {
    asyncData.refresh = refreshInvalidate;
  });

  return data;
}

export function useCacheRef<T = unknown>(key: MaybeRefOrGetter<string>) {
  const nuxt = useNuxtApp();
  return computed<T | undefined>({
    get: () => nuxt.payload.data[CACHE_KEY_PREFIX + toValue(key)],
    set: (value) => {
      if (value === undefined) {
        delete nuxt.payload.data[CACHE_KEY_PREFIX + toValue(key)];
      } else {
        nuxt.payload.data[CACHE_KEY_PREFIX + toValue(key)] = value;
      }
    },
  });
}
johannschopplich commented 2 months ago

With the latest Nuxt versions, you can use the clear function provided in the async data object to clear the fetched data and refetch afterwards:

const { data, refresh, clear } = await useApiData("posts")

async function invalidateAndRefresh() {
  clear()
  await refresh()
}