diff --git a/docs/content/3.docs/1.usage/1.data-fetching.md b/docs/content/3.docs/1.usage/1.data-fetching.md index 75236470c0..04c385f90e 100644 --- a/docs/content/3.docs/1.usage/1.data-fetching.md +++ b/docs/content/3.docs/1.usage/1.data-fetching.md @@ -16,12 +16,12 @@ Within your pages, components and plugins you can use `useAsyncData` to get acce const { data: Ref, pending: Ref, - refresh: (force?: boolean) => Promise, + refresh: () => Promise, error?: any } = useAsyncData( key: string, handler: (ctx?: NuxtApp) => Promise, - options?: { + options?: { lazy: boolean, server: boolean, watch: WatchSource[] @@ -40,6 +40,7 @@ const { * _transform_: a function that can be used to alter `handler` function result after resolving * _pick_: only pick specified keys in this array from `handler` function result * _watch_: watch reactive sources to auto refresh + * _initialCache_: When set to `false`, will skip payload cache for initial fetch. (defaults to `true`) Under the hood, `lazy: false` uses `` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience. diff --git a/packages/nuxt3/src/app/composables/asyncData.ts b/packages/nuxt3/src/app/composables/asyncData.ts index ae164d346e..192558db4d 100644 --- a/packages/nuxt3/src/app/composables/asyncData.ts +++ b/packages/nuxt3/src/app/composables/asyncData.ts @@ -21,12 +21,17 @@ export interface AsyncDataOptions< transform?: Transform pick?: PickKeys watch?: MultiWatchSources + initialCache?: boolean +} + +export interface RefreshOptions { + _initial?: boolean } export interface _AsyncData { data: Ref pending: Ref - refresh: (force?: boolean) => Promise + refresh: (opts?: RefreshOptions) => Promise error?: any } @@ -58,6 +63,7 @@ export function useAsyncData< console.warn('[useAsyncData] `defer` has been renamed to `lazy`. Support for `defer` will be removed in RC.') } options.lazy = options.lazy ?? (options as any).defer ?? false + options.initialCache = options.initialCache ?? true // Setup nuxt instance payload const nuxt = useNuxtApp() @@ -81,11 +87,15 @@ export function useAsyncData< error: ref(nuxt.payload._errors[key] ?? null) } as AsyncData - asyncData.refresh = (force?: boolean) => { + asyncData.refresh = (opts = {}) => { // Avoid fetching same key more than once at a time - if (nuxt._asyncDataPromises[key] && !force) { + if (nuxt._asyncDataPromises[key]) { return nuxt._asyncDataPromises[key] } + // Avoid fetching same key that is already fetched + if (opts._initial && options.initialCache && nuxt.payload.data[key] !== undefined) { + return nuxt.payload.data[key] + } asyncData.pending.value = true // TODO: Cancel previous promise // TODO: Handle immediate errors @@ -115,11 +125,13 @@ export function useAsyncData< return nuxt._asyncDataPromises[key] } + const initialFetch = () => asyncData.refresh({ _initial: true }) + const fetchOnServer = options.server !== false && nuxt.payload.serverRendered // Server side if (process.server && fetchOnServer) { - const promise = asyncData.refresh() + const promise = initialFetch() onServerPrefetch(() => promise) } @@ -131,10 +143,10 @@ export function useAsyncData< } else if (instance && (nuxt.isHydrating || options.lazy)) { // 2. Initial load (server: false): fetch on mounted // 3. Navigation (lazy: true): fetch on mounted - instance._nuxtOnBeforeMountCbs.push(asyncData.refresh) + instance._nuxtOnBeforeMountCbs.push(initialFetch) } else { // 4. Navigation (lazy: false) - or plugin usage: await fetch - asyncData.refresh() + initialFetch() } if (options.watch) { watch(options.watch, () => asyncData.refresh()) diff --git a/packages/nuxt3/src/app/composables/fetch.ts b/packages/nuxt3/src/app/composables/fetch.ts index 03d298e5d0..2a055d8309 100644 --- a/packages/nuxt3/src/app/composables/fetch.ts +++ b/packages/nuxt3/src/app/composables/fetch.ts @@ -7,11 +7,16 @@ import { useAsyncData } from './asyncData' export type FetchResult = TypedInternalResponse -export type UseFetchOptions< +export interface UseFetchOptions< DataT, Transform extends _Transform = _Transform, PickKeys extends KeyOfRes = KeyOfRes -> = AsyncDataOptions & FetchOptions & { key?: string } +> extends + AsyncDataOptions, + FetchOptions + { + key?: string + } export function useFetch< ResT = void, @@ -32,15 +37,22 @@ export function useFetch< return isRef(r) ? r.value : r }) - const asyncData = useAsyncData(key, () => { - return $fetch(_request.value, opts) as Promise<_ResT> - }, { + const _fetchOptions = { + ...opts, + cache: typeof opts.cache === 'boolean' ? undefined : opts.cache + } + + const _asyncDataOptions: AsyncDataOptions = { ...opts, watch: [ _request, ...(opts.watch || []) ] - }) + } + + const asyncData = useAsyncData(key, () => { + return $fetch(_request.value, _fetchOptions) as Promise<_ResT> + }, _asyncDataOptions) return asyncData }