-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
cache support for useFetch/useAsyncData #15445
Comments
I think this is intended via nuxt/framework#8885? 🤔 |
Is there a workaround for this problem? Since the update to version 3.0, every API call with useFetch sends a server request again for me too. Thanks. |
You can implement your own custom cache behaviour by using |
Do you think it could be nice to add an example in the docs? I solved by doing this code: const banners = useState<TBanner[]>('banners');
const pendingBanners = useState<boolean>('pendingBanners');
if (!banners.value) {
const { data, pending } = await useLazyFetch<TBanner[]>('/banners');
banners.value = data.value;
pendingBanners.value = pending.value;
watch(data, (data) => (banners.value = data));
watch(pending, (pending) => (pendingBanners.value = pending));
} |
I've wasted some time today trying to figure out why i was seeing data refetched on back and forth page navigation hehehe. Thought i was going insane and broke something in my datafetching. I thought the built in caching was awesome! |
An example for the documentation would be really helpful. |
That would be a lot of boilerplate code instead of a one-liner, @danielroe… Is this cache removal a final design decision or it is related to the ongoing discussion on nuxt/framework#7569? 🙂 |
Need built-in cache |
Relates to nuxt/framework#8885 Relates to nuxt/framework#8917
Caching fetched data is super useful and having an easy way to do this instead of writing our own cache system would be nice. Makes me wonder if there's any plan to bring a caching feature to What I've noticed (maybe I'm wrong) is that the data is indeed cached. If you go back to the previous page, nuxt uses the previous fetched data to render the page at the same time it makes a new request. Then the data from the cache is updated if the data from that last request has changed. This is clear if you use the throttling option in the DevTools of the browser to make the requests slow. I can understand this behavior being the default way. I think is a good idea. Nonetheless, it would be awesome to have an option to not make new requests unless some event, like browser refresh, expiration time, the refresh() function, etc. It should offload the backend and client from making useless requests from data that won't change anytime soon. Maybe is out of the scope of Nuxt. I think having an easy to use integrated cache system would be nice! |
@danielroe Would be nice to hear your thoughts on this topic as I believe it was very useful feature to have cached version of useAsyncData out of the box. |
I've implemented my own cached version of useAsyncData. Has the same interface but the key is used for caching: import { NuxtApp } from '#app'
import { AsyncData } from 'nuxt/dist/app/composables'
import {
AsyncDataExecuteOptions,
AsyncDataOptions,
KeyOfRes,
PickFrom,
_Transform,
} from 'nuxt/dist/app/composables/asyncData'
export function useCachedAsyncData<Data>(
cacheKey: string,
handler: (ctx?: NuxtApp) => Promise<Data>,
options?: AsyncDataOptions<Data>
): AsyncData<
PickFrom<ReturnType<_Transform<Data>>, KeyOfRes<_Transform<Data>>>,
Error | null
> {
// Used to prevent collisions in nuxt data. Low likelyhood that another property in nuxt data starts with this
const CACHE_KEY_PRREFIX = 'CACHED_ASYNC_DATA'
const { data: cachedData } = useNuxtData(cacheKey)
const cacheKeyAsync = `${CACHE_KEY_PRREFIX}${cacheKey}`
const shouldRefresh = ref<boolean>(false)
const asyncData = useAsyncData<Data, Error>(
cacheKey,
async () => {
await refreshNuxtData(cacheKeyAsync)
// If we already have data, and we're not being forced to refresh, return cached data
if (cachedData.value && !shouldRefresh.value) {
return cachedData.value
}
const result = await handler()
shouldRefresh.value = false
return result
},
options
)
const refresh: (
opts?: AsyncDataExecuteOptions | undefined
) => Promise<void> = async (opts?: AsyncDataExecuteOptions) => {
shouldRefresh.value = true
await asyncData.refresh(opts)
shouldRefresh.value = false
}
return { ...asyncData, refresh }
} |
@MikeBellika awsome, learned a lot from you code :) There is one small issue im trying to fix, when using I guess we have to run the transform on |
@MikeBellika do you have any example of use from this setup? |
@vanling I see. I didn't consider the transform option. The transformed data is not cached. I've put this together quickly, I hope it solves your problem. import { NuxtApp } from '#app';
import { AsyncData } from 'nuxt/dist/app/composables';
import {
AsyncDataExecuteOptions,
AsyncDataOptions,
KeyOfRes,
PickFrom,
_Transform,
} from 'nuxt/dist/app/composables/asyncData';
/**
* Composable for caching data across client requests. Prevents refetching of data when navigating on client side
* Takes the same arguments as `useAsyncData`, with cacheKey being used to deduplicate requests
*/
export function useCachedAsyncData<Data>(
cacheKey: string,
handler: (ctx?: NuxtApp) => Promise<Data>,
options?: AsyncDataOptions<Data>
): AsyncData<
PickFrom<ReturnType<_Transform<Data>>, KeyOfRes<_Transform<Data>>>,
Error | null
> {
// Used to prevent collisions in nuxt data. Low likelyhood that another property in nuxt data starts with this
const CACHE_KEY_PRREFIX = 'CACHED_ASYNC_DATA';
const { data: cachedData } = useNuxtData(cacheKey);
const cacheKeyAsync = `${CACHE_KEY_PRREFIX}${cacheKey}`;
const shouldRefresh = ref<boolean>(false);
// We need to cache transformed value to prevent value from being transformed every time.
const transform = options?.transform;
// Remove transform from options, so useAsyncData doesn't transform it again
const optionsWithoutTransform = { ...options, transform: undefined };
const asyncData = useAsyncData<Data, Error>(
cacheKey,
async () => {
await refreshNuxtData(cacheKeyAsync);
// If we already have data, and we're not being forced to refresh, return cached data
if (cachedData.value && !shouldRefresh.value) {
return cachedData.value;
}
const result = await handler();
shouldRefresh.value = false;
if (transform) {
return transform(result);
}
return result;
},
optionsWithoutTransform
);
const refresh: (opts?: AsyncDataExecuteOptions | undefined) => Promise<void> =
async (opts?: AsyncDataExecuteOptions) => {
shouldRefresh.value = true;
await asyncData.refresh(opts);
shouldRefresh.value = false;
};
return { ...asyncData, refresh };
} @bootsmann1995 Usage should be the same as |
! @MikeBellika awsome! thank you.. i was looking at how i could fix it myself. Never thought about the way you fixed it :) thanks for the learnings |
I have a different solution (simplified), which should be more Nuxt-esque: const cacheKey = computed(() => 'foo')
const cache = true
return useAsyncData<T, FetchError>(
cacheKey.value,
async (nuxt) => {
// Workaround to persist response client-side
// https://github.com/nuxt/framework/issues/8917
if ((nuxt!.isHydrating || cache) && cacheKey.value in nuxt!.payload.data)
return nuxt!.payload.data[cacheKey.value]
const result = $fetch()
if (cache)
nuxt!.payload.data[cacheKey.value] = result
return result
},
_asyncDataOptions,
) as AsyncData<T, FetchError> 👉 I have implemented these additions in |
@danielroe When the team comes agreement on how to do this, I would love to contribute |
Need built-in cache. The browser has caching, there should be a way to cache on nuxt server side |
Unfortunately, the posted solution from @MikeBellika doesn't seem to work correctly when passing the options |
Just learned about this the hard way when I noticed we were making 37 (!!) calls to our get current user endpoint, and none of a few dozen variations on In our case, we have a It looks like the behavior (i.e. removing caching) was changed because of a race condition, which is surprising to me. Is there some architectural limitation that prevented fixing the race condition? Plus, (and my JavaScript internals knowledge is quite limited feel free to tell me I have no idea what I'm talking about), I'd expect the Node/JavaScript event loop to avoid any race conditions in the traditional sense, since all the code actually setting variables and whatnot is happening in a single thread, and no promises and whatnot are being preempted mid-execution. EDIT: Here's a hideous workaround that does the trick for our specific case: const currentUser = useState<User | undefined>(`${composableName}.currentUser`, () => undefined)
const resolvers = useState<Array<() => void>>(`${composableName}.resolvers`, () => [])
const loadCurrentUser = (hardRefresh: boolean = false): Promise<void> => {
// Return the cached user
if (currentUser.value && !hardRefresh) {
return Promise.resolve()
}
// We're already loading a user, wait with everyone else
if (resolvers.value.length > 0) {
return new Promise((resolve) => {
resolvers.value.push(resolve)
})
}
// We're the first to request a user, kick of the request and hop in line at the front of the queue.
return new Promise((resolve) => {
resolvers.value.push(resolve)
$graphql.me({})
.then((resp) => {
currentUser.value = resp.me.user
// Let everyone else know we've loaded the user and clear the queue.
resolvers.value.forEach((fn) => fn())
resolvers.value = []
})
})
} The main problem I see with this, aside from how verbose it is, is that |
@MikeBellika how to revalidate such a cache after a certain time? |
Just discovered this the hard way. I was trying to access a payload via UPDATE: The hydration error could be caused by the fact I do have nested layouts (in UPDATE2: for lack of time I'll currently put the rendered component inside e |
I was trying to implement this workaround and it does not work for me (I don't use SSR) because of this bug. Also for what it's worth, I think it's not unreasonable to expect some level of builtin caching from useFetch. |
to easily cache requests on the client side, check out the new simple example using the default
|
And for more info, I've created a ~10 min video about the new |
Thank you all so much for making this possible! Especially to @manniL, @danielroe and @darioferderber 🤗 |
Environment
Darwin
v16.14.2
3.0.0-rc.14-27802701.2f53495
0.6.2-27796963.837f894
npm@8.8.0
vite
-
-
-
Reproduction
https://stackblitz.com/edit/github-ccqtvp?file=pages%2Fabout.vue,pages%2Findex.vue,package.json
As you can see, on
rc.14 edge
if you click "Go to about page" then "Go to home", you will see that the data will be fetched again.If you change to
rc.13
, the data won't be fetched again, loading from cache.Describe the bug
Everytime
useFetch
is being called, it ma dke a new request, it is not checking for the fetched cached data, as it used to be working byrc.13
Additional context
No response
Logs
No response
The text was updated successfully, but these errors were encountered: