mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
fix(nuxt): use undefined
rather than null
for data fetching defaults (#27294)
This commit is contained in:
parent
a531e6f466
commit
8a759bc6fe
@ -189,6 +189,37 @@ export default defineNuxtConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Default `data` and `error` values in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
|
🚦 **Impact Level**: Minimal
|
||||||
|
|
||||||
|
##### What Changed
|
||||||
|
|
||||||
|
`data` and `error` objects returned from `useAsyncData` will now default to `undefined`.
|
||||||
|
|
||||||
|
##### Reasons for Change
|
||||||
|
|
||||||
|
Previously `data` was initialized to `null` but reset in `clearNuxtData` to `undefined`. `error` was initialized to `null`. This change is to bring greater consistency.
|
||||||
|
|
||||||
|
##### Migration Steps
|
||||||
|
|
||||||
|
If you encounter any issues you can revert back to the previous behavior with:
|
||||||
|
|
||||||
|
```ts twoslash [nuxt.config.ts]
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
experimental: {
|
||||||
|
defaults: {
|
||||||
|
useAsyncData: {
|
||||||
|
value: 'null',
|
||||||
|
errorValue: 'null'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Please report an issue if you are doing this, as we do not plan to keep this as configurable.
|
||||||
|
|
||||||
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
||||||
|
|
||||||
🚦 **Impact Level**: Minimal
|
🚦 **Impact Level**: Minimal
|
||||||
|
6
packages/nuxt/index.d.ts
vendored
6
packages/nuxt/index.d.ts
vendored
@ -19,4 +19,10 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '#app/defaults' {
|
||||||
|
type DefaultAsyncDataErrorValue = undefined
|
||||||
|
type DefaultAsyncDataValue = undefined
|
||||||
|
type DefaultErrorValue = undefined
|
||||||
|
}
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
@ -10,6 +10,9 @@ import { onNuxtReady } from './ready'
|
|||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||||
|
|
||||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>
|
export type _Transform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>
|
||||||
@ -42,7 +45,7 @@ export interface AsyncDataOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* Whether to fetch on the server side.
|
* Whether to fetch on the server side.
|
||||||
@ -117,7 +120,7 @@ export interface _AsyncData<DataT, ErrorT> {
|
|||||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
clear: () => void
|
clear: () => void
|
||||||
error: Ref<ErrorT | null>
|
error: Ref<ErrorT | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,11 +141,11 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -158,7 +161,7 @@ export function useAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -171,12 +174,12 @@ export function useAsyncData<
|
|||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
|
||||||
@ -194,14 +197,14 @@ export function useAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
options?: AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue>
|
||||||
export function useAsyncData<
|
export function useAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
NuxtErrorDataT = unknown,
|
NuxtErrorDataT = unknown,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>) | DefaultAsyncDataErrorValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
|
|
||||||
@ -233,7 +236,7 @@ export function useAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used to get default values
|
// Used to get default values
|
||||||
const getDefault = () => null
|
const getDefault = () => asyncDataDefaults.value
|
||||||
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
|
||||||
|
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
@ -250,11 +253,12 @@ export function useAsyncData<
|
|||||||
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make more precise when v4 lands
|
||||||
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
|
||||||
|
|
||||||
// Create or use a shared asyncData entity
|
// Create or use a shared asyncData entity
|
||||||
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
if (!nuxtApp._asyncData[key] || !options.immediate) {
|
||||||
nuxtApp.payload._errors[key] ??= null
|
nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
|
||||||
|
|
||||||
const _ref = options.deep ? ref : shallowRef
|
const _ref = options.deep ? ref : shallowRef
|
||||||
|
|
||||||
@ -307,7 +311,7 @@ export function useAsyncData<
|
|||||||
nuxtApp.payload.data[key] = result
|
nuxtApp.payload.data[key] = result
|
||||||
|
|
||||||
asyncData.data.value = result
|
asyncData.data.value = result
|
||||||
asyncData.error.value = null
|
asyncData.error.value = asyncDataDefaults.errorValue
|
||||||
asyncData.status.value = 'success'
|
asyncData.status.value = 'success'
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@ -404,11 +408,11 @@ export function useLazyAsyncData<
|
|||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -418,18 +422,18 @@ export function useLazyAsyncData<
|
|||||||
> (
|
> (
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
@ -440,15 +444,15 @@ export function useLazyAsyncData<
|
|||||||
key: string,
|
key: string,
|
||||||
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
handler: (ctx?: NuxtApp) => Promise<ResT>,
|
||||||
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
options?: Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue>
|
||||||
|
|
||||||
export function useLazyAsyncData<
|
export function useLazyAsyncData<
|
||||||
ResT,
|
ResT,
|
||||||
DataE = Error,
|
DataE = Error,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | null> {
|
> (...args: any[]): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, DataE | DefaultAsyncDataValue> {
|
||||||
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
|
||||||
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
|
||||||
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise<ResT>, AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>]
|
||||||
@ -463,12 +467,12 @@ export function useLazyAsyncData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.1.0 */
|
/** @since 3.1.0 */
|
||||||
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | null> } {
|
export function useNuxtData<DataT = any> (key: string): { data: Ref<DataT | DefaultAsyncDataValue> } {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
// Initialize value when key is not already set
|
// Initialize value when key is not already set
|
||||||
if (!(key in nuxtApp.payload.data)) {
|
if (!(key in nuxtApp.payload.data)) {
|
||||||
nuxtApp.payload.data[key] = null
|
nuxtApp.payload.data[key] = asyncDataDefaults.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -520,12 +524,12 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key in nuxtApp.payload._errors) {
|
if (key in nuxtApp.payload._errors) {
|
||||||
nuxtApp.payload._errors[key] = null
|
nuxtApp.payload._errors[key] = asyncDataDefaults.errorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxtApp._asyncData[key]) {
|
if (nuxtApp._asyncData[key]) {
|
||||||
nuxtApp._asyncData[key]!.data.value = undefined
|
nuxtApp._asyncData[key]!.data.value = undefined
|
||||||
nuxtApp._asyncData[key]!.error.value = null
|
nuxtApp._asyncData[key]!.error.value = asyncDataDefaults.errorValue
|
||||||
nuxtApp._asyncData[key]!.pending.value = false
|
nuxtApp._asyncData[key]!.pending.value = false
|
||||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ import { toRef } from 'vue'
|
|||||||
import { useNuxtApp } from '../nuxt'
|
import { useNuxtApp } from '../nuxt'
|
||||||
import { useRouter } from './router'
|
import { useRouter } from './router'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
@ -47,7 +50,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
|
|||||||
await useRouter().replace(options.redirect)
|
await useRouter().replace(options.redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
error.value = null
|
error.value = nuxtDefaultErrorValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
|
@ -8,6 +8,9 @@ import { useRequestFetch } from './ssr'
|
|||||||
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
|
||||||
import { useAsyncData } from './asyncData'
|
import { useAsyncData } from './asyncData'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
import { fetchDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ export interface UseFetchOptions<
|
|||||||
ResT,
|
ResT,
|
||||||
DataT = ResT,
|
DataT = ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
R extends NitroFetchRequest = string & {},
|
R extends NitroFetchRequest = string & {},
|
||||||
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>,
|
||||||
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
|
||||||
@ -54,11 +57,11 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
/**
|
/**
|
||||||
* Fetch data from an API endpoint with an SSR-friendly composable.
|
* Fetch data from an API endpoint with an SSR-friendly composable.
|
||||||
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
|
||||||
@ -77,7 +80,7 @@ export function useFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useFetch<
|
export function useFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -86,7 +89,7 @@ export function useFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
|
||||||
@ -190,11 +193,11 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -207,7 +210,7 @@ export function useLazyFetch<
|
|||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
opts?: Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>
|
||||||
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null>
|
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
|
||||||
export function useLazyFetch<
|
export function useLazyFetch<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
ErrorT = FetchError,
|
ErrorT = FetchError,
|
||||||
@ -216,7 +219,7 @@ export function useLazyFetch<
|
|||||||
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
|
||||||
DataT = _ResT,
|
DataT = _ResT,
|
||||||
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
|
||||||
DefaultT = null,
|
DefaultT = DefaultAsyncDataValue,
|
||||||
> (
|
> (
|
||||||
request: Ref<ReqT> | ReqT | (() => ReqT),
|
request: Ref<ReqT> | ReqT | (() => ReqT),
|
||||||
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
arg1?: string | Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 'lazy'>,
|
||||||
|
2
packages/nuxt/src/app/defaults.js
Normal file
2
packages/nuxt/src/app/defaults.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
export {}
|
@ -23,6 +23,8 @@ import type { ViewTransition } from './plugins/view-transitions.client'
|
|||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appId } from '#build/nuxt.config.mjs'
|
import { appId } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
|
||||||
import type { NuxtAppLiterals } from '#app'
|
import type { NuxtAppLiterals } from '#app'
|
||||||
|
|
||||||
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
|
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
|
||||||
@ -92,8 +94,8 @@ export interface NuxtPayload {
|
|||||||
state: Record<string, any>
|
state: Record<string, any>
|
||||||
once: Set<string>
|
once: Set<string>
|
||||||
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
config?: Pick<RuntimeConfig, 'public' | 'app'>
|
||||||
error?: NuxtError | null
|
error?: NuxtError | DefaultErrorValue
|
||||||
_errors: Record<string, NuxtError | null>
|
_errors: Record<string, NuxtError | DefaultAsyncDataErrorValue>
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +124,7 @@ interface _NuxtApp {
|
|||||||
_asyncData: Record<string, {
|
_asyncData: Record<string, {
|
||||||
data: Ref<any>
|
data: Ref<any>
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
error: Ref<Error | null>
|
error: Ref<Error | DefaultAsyncDataErrorValue>
|
||||||
status: Ref<AsyncDataRequestStatus>
|
status: Ref<AsyncDataRequestStatus>
|
||||||
} | undefined>
|
} | undefined>
|
||||||
|
|
||||||
|
@ -130,6 +130,12 @@ declare module '#app' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '#app/defaults' {
|
||||||
|
type DefaultAsyncDataErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultAsyncDataValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
type DefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
interface ComponentCustomProperties extends NuxtAppInjections { }
|
interface ComponentCustomProperties extends NuxtAppInjections { }
|
||||||
}
|
}
|
||||||
@ -393,7 +399,12 @@ export const nuxtConfigTemplate: NuxtTemplate = {
|
|||||||
`export const devRootDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.rootDir) : 'null'}`,
|
`export const devRootDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.rootDir) : 'null'}`,
|
||||||
`export const devLogs = ${JSON.stringify(ctx.nuxt.options.features.devLogs)}`,
|
`export const devLogs = ${JSON.stringify(ctx.nuxt.options.features.devLogs)}`,
|
||||||
`export const nuxtLinkDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.nuxtLink)}`,
|
`export const nuxtLinkDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.nuxtLink)}`,
|
||||||
`export const asyncDataDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.useAsyncData)}`,
|
`export const asyncDataDefaults = ${JSON.stringify({
|
||||||
|
...ctx.nuxt.options.experimental.defaults.useAsyncData,
|
||||||
|
value: ctx.nuxt.options.experimental.defaults.useAsyncData.value === 'null' ? null : undefined,
|
||||||
|
errorValue: ctx.nuxt.options.experimental.defaults.useAsyncData.errorValue === 'null' ? null : undefined,
|
||||||
|
})}`,
|
||||||
|
`export const nuxtDefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}`,
|
||||||
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
|
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
|
||||||
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
|
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
|
||||||
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
|
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
|
||||||
|
@ -415,6 +415,18 @@ export default defineUntypedSchema({
|
|||||||
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
|
||||||
*/
|
*/
|
||||||
useAsyncData: {
|
useAsyncData: {
|
||||||
|
/** @type {'undefined' | 'null'} */
|
||||||
|
value: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'undefined' : 'null')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/** @type {'undefined' | 'null'} */
|
||||||
|
errorValue: {
|
||||||
|
async $resolve (val, get) {
|
||||||
|
return val ?? ((await get('future') as Record<string, unknown>).compatibilityVersion === 4 ? 'undefined' : 'null')
|
||||||
|
},
|
||||||
|
},
|
||||||
deep: {
|
deep: {
|
||||||
async $resolve (val, get) {
|
async $resolve (val, get) {
|
||||||
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
return val ?? !((await get('future') as Record<string, unknown>).compatibilityVersion === 4)
|
||||||
|
@ -2433,21 +2433,23 @@ describe.skipIf(isWindows)('useAsyncData', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('data is null after navigation when immediate false', async () => {
|
it('data is null after navigation when immediate false', async () => {
|
||||||
|
const defaultValue = isV4 ? 'undefined' : 'null'
|
||||||
|
|
||||||
const { page } = await renderPage('/useAsyncData/immediate-remove-unmounted')
|
const { page } = await renderPage('/useAsyncData/immediate-remove-unmounted')
|
||||||
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
|
expect(await page.locator('#immediate-data').getByText(defaultValue).textContent()).toBe(defaultValue)
|
||||||
|
|
||||||
await page.click('#execute-btn')
|
await page.click('#execute-btn')
|
||||||
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
|
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain(defaultValue)
|
||||||
|
|
||||||
await page.click('#to-index')
|
await page.click('#to-index')
|
||||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/')
|
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/')
|
||||||
|
|
||||||
await page.click('#to-immediate-remove-unmounted')
|
await page.click('#to-immediate-remove-unmounted')
|
||||||
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/useAsyncData/immediate-remove-unmounted')
|
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/useAsyncData/immediate-remove-unmounted')
|
||||||
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
|
expect(await page.locator('#immediate-data').getByText(defaultValue).textContent()).toBe(defaultValue)
|
||||||
|
|
||||||
await page.click('#execute-btn')
|
await page.click('#execute-btn')
|
||||||
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
|
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain(defaultValue)
|
||||||
|
|
||||||
await page.close()
|
await page.close()
|
||||||
})
|
})
|
||||||
|
97
test/fixtures/basic-types/types.ts
vendored
97
test/fixtures/basic-types/types.ts
vendored
@ -11,6 +11,9 @@ import type { NavigateToOptions } from '#app/composables/router'
|
|||||||
import { NuxtLayout, NuxtLink, NuxtPage, ServerComponent, WithTypes } from '#components'
|
import { NuxtLayout, NuxtLink, NuxtPage, ServerComponent, WithTypes } from '#components'
|
||||||
import { useRouter } from '#imports'
|
import { useRouter } from '#imports'
|
||||||
|
|
||||||
|
// TODO: temporary module for backwards compatibility
|
||||||
|
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||||
|
|
||||||
interface TestResponse { message: string }
|
interface TestResponse { message: string }
|
||||||
|
|
||||||
describe('API routes', () => {
|
describe('API routes', () => {
|
||||||
@ -31,61 +34,61 @@ describe('API routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('works with useAsyncData', () => {
|
it('works with useAsyncData', () => {
|
||||||
expectTypeOf(useAsyncData('api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useAsyncData('api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useAsyncData('api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useAsyncData('api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useAsyncData('api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useAsyncData<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useAsyncData<TestResponse>('api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<unknown> | null>>()
|
expectTypeOf(useAsyncData('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<unknown> | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
expectTypeOf(useAsyncData<any, string>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | DefaultAsyncDataErrorValue>>()
|
||||||
// backwards compatibility
|
// backwards compatibility
|
||||||
expectTypeOf(useAsyncData<any, Error>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
expectTypeOf(useAsyncData<any, Error>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useAsyncData<any, NuxtError<string>>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | null>>()
|
expectTypeOf(useAsyncData<any, NuxtError<string>>('api-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<NuxtError<string> | DefaultAsyncDataErrorValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hello', () => $fetch('/api/hello')).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hey', () => $fetch('/api/hey')).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-hey-with-pick', () => $fetch('/api/hey'), { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-union', () => $fetch('/api/union')).data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-union-with-pick', () => $fetch('/api/union'), { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData('lazy-api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useLazyAsyncData('lazy-api-other', () => $fetch('/api/other')).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useLazyAsyncData<TestResponse>('lazy-api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useLazyAsyncData<TestResponse>('lazy-api-generics', () => $fetch('/test')).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyAsyncData('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | null>>()
|
expectTypeOf(useLazyAsyncData('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<Error | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useLazyAsyncData<any, string>('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData<any, string>('lazy-error-generics', () => $fetch('/error')).error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('works with useFetch', () => {
|
it('works with useFetch', () => {
|
||||||
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'GET' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'GET' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'get' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'get' }).data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'POST' }).data).toEqualTypeOf<Ref<{ method: 'post' } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'POST' }).data).toEqualTypeOf<Ref<{ method: 'post' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/hey', { method: 'post' }).data).toEqualTypeOf<Ref<{ method: 'post' } | null>>()
|
expectTypeOf(useFetch('/api/hey', { method: 'post' }).data).toEqualTypeOf<Ref<{ method: 'post' } | DefaultAsyncDataValue>>()
|
||||||
// @ts-expect-error not a valid method
|
// @ts-expect-error not a valid method
|
||||||
useFetch('/api/hey', { method: 'PATCH' })
|
useFetch('/api/hey', { method: 'PATCH' })
|
||||||
expectTypeOf(useFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useFetch<TestResponse>('/test', { method: 'POST' }).data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useFetch<TestResponse>('/test', { method: 'POST' }).data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<FetchError | null>>()
|
expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<FetchError | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/hey').data).toEqualTypeOf<Ref<{ foo: string, baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/hey', { pick: ['baz'] }).data).toEqualTypeOf<Ref<{ baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | null>>()
|
expectTypeOf(useLazyFetch('/api/union').data).toEqualTypeOf<Ref<{ type: 'a', foo: string } | { type: 'b', baz: string } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | null>>()
|
expectTypeOf(useLazyFetch('/api/union', { pick: ['type'] }).data).toEqualTypeOf<Ref<{ type: 'a' } | { type: 'b' } | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useLazyFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
expectTypeOf(useLazyFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | null>>()
|
expectTypeOf(useLazyFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<FetchError | null>>()
|
expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<FetchError | DefaultAsyncDataErrorValue>>()
|
||||||
expectTypeOf(useLazyFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | DefaultAsyncDataErrorValue>>()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -421,10 +424,10 @@ describe('composables', () => {
|
|||||||
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { default: () => 'test' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { default: () => 'test' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
|
|
||||||
// transform must match the explicit generic because of typescript limitations microsoft/TypeScript#14400
|
// transform must match the explicit generic because of typescript limitations microsoft/TypeScript#14400
|
||||||
expectTypeOf(useFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch<string>('/test', { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyAsyncData<string>(() => $fetch('/test'), { transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
expectTypeOf(useFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
expectTypeOf(useLazyFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
expectTypeOf(useLazyFetch<string>('/test', { default: () => 'test', transform: () => 'transformed' }).data).toEqualTypeOf<Ref<string>>()
|
||||||
@ -439,7 +442,7 @@ describe('composables', () => {
|
|||||||
return data.foo
|
return data.foo
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expectTypeOf(data).toEqualTypeOf<Ref<'bar' | null>>()
|
expectTypeOf(data).toEqualTypeOf<Ref<'bar' | DefaultAsyncDataValue>>()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('infer request url string literal from server/api routes', () => {
|
it('infer request url string literal from server/api routes', () => {
|
||||||
@ -448,8 +451,8 @@ describe('composables', () => {
|
|||||||
expectTypeOf(useFetch(dynamicStringUrl).data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch(dynamicStringUrl).data).toEqualTypeOf<Ref<unknown>>()
|
||||||
|
|
||||||
// request param should infer string literal type / show auto-complete hint base on server routes, ex: '/api/hello'
|
// request param should infer string literal type / show auto-complete hint base on server routes, ex: '/api/hello'
|
||||||
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | null>>()
|
expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string | DefaultAsyncDataValue>>()
|
||||||
|
|
||||||
// request can accept string literal and Request object type
|
// request can accept string literal and Request object type
|
||||||
expectTypeOf(useFetch('https://example.com/api').data).toEqualTypeOf<Ref<unknown>>()
|
expectTypeOf(useFetch('https://example.com/api').data).toEqualTypeOf<Ref<unknown>>()
|
||||||
@ -519,7 +522,7 @@ describe('composables', () => {
|
|||||||
it('correctly types returns when using with getCachedData', () => {
|
it('correctly types returns when using with getCachedData', () => {
|
||||||
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
expectTypeOf(useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
||||||
getCachedData: key => useNuxtApp().payload.data[key],
|
getCachedData: key => useNuxtApp().payload.data[key],
|
||||||
}).data).toEqualTypeOf<Ref<{ foo: number } | null>>()
|
}).data).toEqualTypeOf<Ref<{ foo: number } | DefaultAsyncDataValue>>()
|
||||||
useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
useAsyncData('test', () => Promise.resolve({ foo: 1 }), {
|
||||||
// @ts-expect-error cached data should return the same as value of fetcher
|
// @ts-expect-error cached data should return the same as value of fetcher
|
||||||
getCachedData: () => ({ bar: 2 }),
|
getCachedData: () => ({ bar: 2 }),
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div>immediate-remove-unmounted.vue</div>
|
<div>immediate-remove-unmounted.vue</div>
|
||||||
<div id="immediate-data">
|
<div id="immediate-data">
|
||||||
{{ data === null ? "null" : data }}
|
{{ data === null ? "null" : (data === undefined ? 'undefined' : data) }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="execute-btn"
|
id="execute-btn"
|
||||||
@ -20,9 +20,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { asyncDataDefaults } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
const { data, execute } = await useAsyncData('immediateFalse', () => $fetch('/api/random'), { immediate: false })
|
const { data, execute } = await useAsyncData('immediateFalse', () => $fetch('/api/random'), { immediate: false })
|
||||||
|
|
||||||
if (data.value !== null) {
|
if (data.value !== asyncDataDefaults.errorValue) {
|
||||||
throw new Error('Initial data should be null: ' + data.value)
|
throw new Error(`Initial data should be ${asyncDataDefaults.errorValue}: ` + data.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -20,6 +20,9 @@ import { callOnce } from '#app/composables/once'
|
|||||||
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
import { useLoadingIndicator } from '#app/composables/loading-indicator'
|
||||||
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
import { useRouteAnnouncer } from '#app/composables/route-announcer'
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
import { asyncDataDefaults, nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
registerEndpoint('/api/test', defineEventHandler(event => ({
|
registerEndpoint('/api/test', defineEventHandler(event => ({
|
||||||
method: event.method,
|
method: event.method,
|
||||||
headers: Object.fromEntries(event.headers.entries()),
|
headers: Object.fromEntries(event.headers.entries()),
|
||||||
@ -126,7 +129,7 @@ describe('useAsyncData', () => {
|
|||||||
]
|
]
|
||||||
`)
|
`)
|
||||||
expect(res instanceof Promise).toBeTruthy()
|
expect(res instanceof Promise).toBeTruthy()
|
||||||
expect(res.data.value).toBe(null)
|
expect(res.data.value).toBe(asyncDataDefaults.value)
|
||||||
await res
|
await res
|
||||||
expect(res.data.value).toBe('test')
|
expect(res.data.value).toBe('test')
|
||||||
})
|
})
|
||||||
@ -138,7 +141,7 @@ describe('useAsyncData', () => {
|
|||||||
expect(immediate.pending.value).toBe(false)
|
expect(immediate.pending.value).toBe(false)
|
||||||
|
|
||||||
const nonimmediate = await useAsyncData(() => Promise.resolve('test'), { immediate: false })
|
const nonimmediate = await useAsyncData(() => Promise.resolve('test'), { immediate: false })
|
||||||
expect(nonimmediate.data.value).toBe(null)
|
expect(nonimmediate.data.value).toBe(asyncDataDefaults.value)
|
||||||
expect(nonimmediate.status.value).toBe('idle')
|
expect(nonimmediate.status.value).toBe('idle')
|
||||||
expect(nonimmediate.pending.value).toBe(true)
|
expect(nonimmediate.pending.value).toBe(true)
|
||||||
})
|
})
|
||||||
@ -163,9 +166,9 @@ describe('useAsyncData', () => {
|
|||||||
// https://github.com/nuxt/nuxt/issues/23411
|
// https://github.com/nuxt/nuxt/issues/23411
|
||||||
it('should initialize with error set to null when immediate: false', async () => {
|
it('should initialize with error set to null when immediate: false', async () => {
|
||||||
const { error, execute } = useAsyncData(() => ({}), { immediate: false })
|
const { error, execute } = useAsyncData(() => ({}), { immediate: false })
|
||||||
expect(error.value).toBe(null)
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
await execute()
|
await execute()
|
||||||
expect(error.value).toBe(null)
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be accessible with useNuxtData', async () => {
|
it('should be accessible with useNuxtData', async () => {
|
||||||
@ -206,8 +209,9 @@ describe('useAsyncData', () => {
|
|||||||
|
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
// TODO: update to asyncDataDefaults.value in v4
|
||||||
expect(data.value).toBeUndefined()
|
expect(data.value).toBeUndefined()
|
||||||
expect(error.value).toBeNull()
|
expect(error.value).toBe(asyncDataDefaults.errorValue)
|
||||||
expect(pending.value).toBe(false)
|
expect(pending.value).toBe(false)
|
||||||
expect(status.value).toBe('idle')
|
expect(status.value).toBe('idle')
|
||||||
})
|
})
|
||||||
@ -345,13 +349,12 @@ describe('errors', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('global nuxt errors', () => {
|
it('global nuxt errors', () => {
|
||||||
const err = useError()
|
const error = useError()
|
||||||
expect(err.value).toBeUndefined()
|
expect(error.value).toBeUndefined()
|
||||||
showError('new error')
|
showError('new error')
|
||||||
expect(err.value).toMatchInlineSnapshot('[Error: new error]')
|
expect(error.value).toMatchInlineSnapshot('[Error: new error]')
|
||||||
clearError()
|
clearError()
|
||||||
// TODO: should this return to being undefined?
|
expect(error.value).toBe(nuxtDefaultErrorValue)
|
||||||
expect(err.value).toBeNull()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -616,7 +619,7 @@ describe('routing utilities: `abortNavigation`', () => {
|
|||||||
it('should throw an error if one is provided', () => {
|
it('should throw an error if one is provided', () => {
|
||||||
const error = useError()
|
const error = useError()
|
||||||
expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot('[Error: Page not found]')
|
expect(() => abortNavigation({ message: 'Page not found' })).toThrowErrorMatchingInlineSnapshot('[Error: Page not found]')
|
||||||
expect(error.value).toBeFalsy()
|
expect(error.value).toBe(nuxtDefaultErrorValue)
|
||||||
})
|
})
|
||||||
it('should block navigation if no error is provided', () => {
|
it('should block navigation if no error is provided', () => {
|
||||||
expect(abortNavigation()).toMatchInlineSnapshot('false')
|
expect(abortNavigation()).toMatchInlineSnapshot('false')
|
||||||
|
Loading…
Reference in New Issue
Block a user