diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts
index b7c401aaa3..a5bd1e882d 100644
--- a/packages/nuxt/src/app/composables/asyncData.ts
+++ b/packages/nuxt/src/app/composables/asyncData.ts
@@ -9,12 +9,12 @@ export type _Transform = (input: Input) => Output
export type PickFrom> = T extends Array
? T
: T extends Record
- ? keyof T extends K[number]
- ? T // Exact same keys as the target, skip Pick
- : K[number] extends never
- ? T
- : Pick
- : T
+ ? keyof T extends K[number]
+ ? T // Exact same keys as the target, skip Pick
+ : K[number] extends never
+ ? T
+ : Pick
+ : T
export type KeysOf = Array<
T extends T // Include all keys of union types, not just common keys
@@ -32,11 +32,12 @@ export interface AsyncDataOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> {
server?: boolean
lazy?: boolean
- default?: () => DataT | Ref | null
- transform?: _Transform,
+ default?: () => DefaultT | Ref
+ transform?: _Transform
pick?: PickKeys
watch?: MultiWatchSources
immediate?: boolean
@@ -53,7 +54,7 @@ export interface AsyncDataExecuteOptions {
}
export interface _AsyncData {
- data: Ref
+ data: Ref
pending: Ref
refresh: (opts?: AsyncDataExecuteOptions) => Promise
execute: (opts?: AsyncDataExecuteOptions) => Promise
@@ -67,32 +68,35 @@ export function useAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
handler: (ctx?: NuxtApp) => Promise,
- options?: AsyncDataOptions
-): AsyncData, DataE | null>
+ options?: AsyncDataOptions
+): AsyncData | DefaultT, DataE | null>
export function useAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
- options?: AsyncDataOptions
-): AsyncData, DataE | null>
+ options?: AsyncDataOptions
+): AsyncData | DefaultT, DataE | null>
export function useAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (...args: any[]): AsyncData, DataE | null> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
// eslint-disable-next-line prefer-const
- let [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
+ let [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
// Validate arguments
if (typeof key !== 'string') {
@@ -104,7 +108,7 @@ export function useAsyncData<
// Apply defaults
options.server = options.server ?? true
- options.default = options.default ?? getDefault
+ options.default = options.default ?? (getDefault as () => DefaultT)
options.lazy = options.lazy ?? false
options.immediate = options.immediate ?? true
@@ -118,13 +122,13 @@ export function useAsyncData<
// Create or use a shared asyncData entity
if (!nuxt._asyncData[key]) {
nuxt._asyncData[key] = {
- data: ref(getCachedData() ?? options.default?.() ?? null),
+ data: ref(getCachedData() ?? options.default!()),
pending: ref(!hasCachedData()),
error: toRef(nuxt.payload._errors, key)
}
}
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
- const asyncData = { ...nuxt._asyncData[key] } as AsyncData
+ const asyncData = { ...nuxt._asyncData[key] } as AsyncData
asyncData.refresh = asyncData.execute = (opts = {}) => {
if (nuxt._asyncDataPromises[key]) {
@@ -167,7 +171,7 @@ export function useAsyncData<
if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] }
asyncData.error.value = error
- asyncData.data.value = unref(options.default?.() ?? null)
+ asyncData.data.value = unref(options.default!())
})
.finally(() => {
if ((promise as any).cancelled) { return }
@@ -248,30 +252,33 @@ export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
handler: (ctx?: NuxtApp) => Promise,
- options?: Omit, 'lazy'>
-): AsyncData, DataE | null>
+ options?: Omit, 'lazy'>
+): AsyncData | DefaultT, DataE | null>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
- options?: Omit, 'lazy'>
-): AsyncData, DataE | null>
+ options?: Omit, 'lazy'>
+): AsyncData | DefaultT, DataE | null>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
- PickKeys extends KeysOf = KeysOf
-> (...args: any[]): AsyncData, DataE | null> {
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
+> (...args: any[]): AsyncData | DefaultT, DataE | null> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
- const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
+ const [key, handler, options] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
// @ts-expect-error we pass an extra argument to prevent a key being injected
return useAsyncData(key, handler, { ...options, lazy: true }, null)
}
diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts
index c06271cb43..ba77fe8c24 100644
--- a/packages/nuxt/src/app/composables/fetch.ts
+++ b/packages/nuxt/src/app/composables/fetch.ts
@@ -19,9 +19,10 @@ export interface UseFetchOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
R extends NitroFetchRequest = string & {},
M extends AvailableRouterMethod = AvailableRouterMethod
-> extends Omit, 'watch'>, ComputedFetchOptions {
+> extends Omit, 'watch'>, ComputedFetchOptions {
key?: string
$fetch?: typeof globalThis.$fetch
watch?: MultiWatchSources | false
@@ -34,11 +35,12 @@ export function useFetch<
Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod,
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
request: Ref | ReqT | (() => ReqT),
- opts?: UseFetchOptions<_ResT, DataT, PickKeys, ReqT, Method>
-): AsyncData, ErrorT | null>
+ opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
+): AsyncData | DefaultT, ErrorT | null>
export function useFetch<
ResT = void,
ErrorT = FetchError,
@@ -46,10 +48,11 @@ export function useFetch<
Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod,
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
request: Ref | ReqT | (() => ReqT),
- arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, ReqT, Method>,
+ arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
arg2?: string
) {
const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
@@ -91,7 +94,7 @@ export function useFetch<
cache: typeof opts.cache === 'boolean' ? undefined : opts.cache
})
- const _asyncDataOptions: AsyncDataOptions<_ResT, DataT, PickKeys> = {
+ const _asyncDataOptions: AsyncDataOptions<_ResT, DataT, PickKeys, DefaultT> = {
server,
lazy,
default: defaultFn,
@@ -103,7 +106,7 @@ export function useFetch<
let controller: AbortController
- const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys>(key, () => {
+ const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys, DefaultT>(key, () => {
controller?.abort?.()
controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController
@@ -127,11 +130,12 @@ export function useLazyFetch<
Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod,
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
request: Ref | ReqT | (() => ReqT),
- opts?: Omit, 'lazy'>
-): AsyncData, ErrorT | null>
+ opts?: Omit, 'lazy'>
+): AsyncData | DefaultT, ErrorT | null>
export function useLazyFetch<
ResT = void,
ErrorT = FetchError,
@@ -139,15 +143,16 @@ export function useLazyFetch<
Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod,
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
- PickKeys extends KeysOf = KeysOf
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = null,
> (
request: Ref | ReqT | (() => ReqT),
- arg1?: string | Omit, 'lazy'>,
+ arg1?: string | Omit, 'lazy'>,
arg2?: string
) {
const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2]
- return useFetch(request, {
+ return useFetch(request, {
...opts,
lazy: true
},
diff --git a/test/fixtures/basic/types.ts b/test/fixtures/basic/types.ts
index 1b61186a60..cd3b1386dd 100644
--- a/test/fixtures/basic/types.ts
+++ b/test/fixtures/basic/types.ts
@@ -275,13 +275,13 @@ describe('composables', () => {
expectTypeOf(useCookie('test', { default: () => 500 })).toEqualTypeOf[>()
useCookie('test').value = null
- expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => ref(500) }).data).toEqualTypeOf][>()
- expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => 500 }).data).toEqualTypeOf][>()
- expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => ref(500) }).data).toEqualTypeOf][>()
- expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => 500 }).data).toEqualTypeOf][>()
+ expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => ref(500) }).data).toEqualTypeOf][>()
+ expectTypeOf(useAsyncData('test', () => Promise.resolve(500), { default: () => 500 }).data).toEqualTypeOf][>()
+ expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => ref(500) }).data).toEqualTypeOf][>()
+ expectTypeOf(useAsyncData('test', () => Promise.resolve('500'), { default: () => 500 }).data).toEqualTypeOf][>()
- expectTypeOf(useFetch('/test', { default: () => ref(500) }).data).toEqualTypeOf][>()
- expectTypeOf(useFetch('/test', { default: () => 500 }).data).toEqualTypeOf][>()
+ expectTypeOf(useFetch('/test', { default: () => ref(500) }).data).toEqualTypeOf][>()
+ expectTypeOf(useFetch('/test', { default: () => 500 }).data).toEqualTypeOf][>()
})
it('infer request url string literal from server/api routes', () => {
@@ -313,11 +313,11 @@ describe('composables', () => {
.toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
// Default values: #14437
- expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo }).data).toEqualTypeOf][>()
+ expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo }).data).toEqualTypeOf][>()
expectTypeOf(useLazyAsyncData('test', () => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo }))
.toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: { bar: 500 } }), { default: () => ({ bar: 500 }), transform: v => v.foo }))
- expectTypeOf(useFetch('/api/hey', { default: () => 'bar', transform: v => v.foo }).data).toEqualTypeOf][>()
- expectTypeOf(useLazyFetch('/api/hey', { default: () => 'bar', transform: v => v.foo }).data).toEqualTypeOf][>()
+ expectTypeOf(useFetch('/api/hey', { default: () => 1, transform: v => v.foo }).data).toEqualTypeOf][>()
+ expectTypeOf(useLazyFetch('/api/hey', { default: () => 'bar', transform: v => v.foo }).data).toEqualTypeOf][>()
})
it('uses types compatible between useRequestHeaders and useFetch', () => {
]