feat(nuxt): add immediate option for useAsyncData and useFetch (#5500)

Co-authored-by: Pooya Parsa <pooya@pi0.io>
This commit is contained in:
Vl4dimyr 2022-09-07 11:47:40 +02:00 committed by GitHub
parent 577a7b681e
commit fc2be9ed42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 8 deletions

View File

@ -23,6 +23,7 @@ type AsyncDataOptions<DataT> = {
pick?: string[] pick?: string[]
watch?: WatchSource[] watch?: WatchSource[]
initialCache?: boolean initialCache?: boolean
immediate?: boolean
} }
interface RefreshOptions { interface RefreshOptions {
@ -32,6 +33,7 @@ interface RefreshOptions {
type AsyncData<DataT, ErrorT> = { type AsyncData<DataT, ErrorT> = {
data: Ref<DataT | null> data: Ref<DataT | null>
pending: Ref<boolean> pending: Ref<boolean>
execute: () => Promise<void>
refresh: (opts?: RefreshOptions) => Promise<void> refresh: (opts?: RefreshOptions) => Promise<void>
error: Ref<ErrorT | null> error: Ref<ErrorT | null>
} }
@ -51,6 +53,7 @@ type AsyncData<DataT, ErrorT> = {
* _pick_: only pick specified keys in this array from the `handler` function result * _pick_: only pick specified keys in this array from the `handler` function result
* _watch_: watch reactive sources to auto-refresh * _watch_: watch reactive sources to auto-refresh
* _initialCache_: When set to `false`, will skip payload cache for initial fetch. (defaults to `true`) * _initialCache_: When set to `false`, will skip payload cache for initial fetch. (defaults to `true`)
* _immediate_: When set to `false`, will prevent the request from firing immediately. (defaults to `true`)
Under the hood, `lazy: false` uses `<Suspense>` 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. Under the hood, `lazy: false` uses `<Suspense>` 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.
@ -58,7 +61,7 @@ Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the rout
* **data**: the result of the asynchronous function that is passed in * **data**: the result of the asynchronous function that is passed in
* **pending**: a boolean indicating whether the data is still being fetched * **pending**: a boolean indicating whether the data is still being fetched
* **refresh**: a function that can be used to refresh the data returned by the `handler` function * **refresh**/**execute**: a function that can be used to refresh the data returned by the `handler` function
* **error**: an error object if the data fetching failed * **error**: an error object if the data fetching failed
By default, Nuxt waits until a `refresh` is finished before it can be executed again. By default, Nuxt waits until a `refresh` is finished before it can be executed again.

View File

@ -19,6 +19,7 @@ type UseFetchOptions = {
baseURL?: string baseURL?: string
server?: boolean server?: boolean
lazy?: boolean lazy?: boolean
immediate?: boolean
default?: () => DataT default?: () => DataT
transform?: (input: DataT) => DataT transform?: (input: DataT) => DataT
pick?: string[] pick?: string[]
@ -30,6 +31,7 @@ type AsyncData<DataT> = {
data: Ref<DataT> data: Ref<DataT>
pending: Ref<boolean> pending: Ref<boolean>
refresh: () => Promise<void> refresh: () => Promise<void>
execute: () => Promise<void>
error: Ref<Error | boolean> error: Ref<Error | boolean>
} }
``` ```
@ -51,6 +53,7 @@ type AsyncData<DataT> = {
* `watch`: watch reactive sources to auto-refresh. * `watch`: watch reactive sources to auto-refresh.
* `initialCache`: When set to `false`, will skip payload cache for initial fetch (defaults to `true`). * `initialCache`: When set to `false`, will skip payload cache for initial fetch (defaults to `true`).
* `transform`: A function that can be used to alter `handler` function result after resolving. * `transform`: A function that can be used to alter `handler` function result after resolving.
* `immediate`: When set to `false`, will prevent the request from firing immediately. (defaults to `true`)
::alert{type=warning} ::alert{type=warning}
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`. If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.
@ -60,7 +63,7 @@ If you provide a function or ref as the `url` parameter, or if you provide funct
* **data**: the result of the asynchronous function that is passed in. * **data**: the result of the asynchronous function that is passed in.
* **pending**: a boolean indicating whether the data is still being fetched. * **pending**: a boolean indicating whether the data is still being fetched.
* **refresh**: a function that can be used to refresh the data returned by the `handler` function. * **refresh**/**execute** : a function that can be used to refresh the data returned by the `handler` function.
* **error**: an error object if the data fetching failed. * **error**: an error object if the data fetching failed.
By default, Nuxt waits until a `refresh` is finished before it can be executed again. By default, Nuxt waits until a `refresh` is finished before it can be executed again.

View File

@ -29,16 +29,18 @@ export interface AsyncDataOptions<
pick?: PickKeys pick?: PickKeys
watch?: MultiWatchSources watch?: MultiWatchSources
initialCache?: boolean initialCache?: boolean
immediate?: boolean
} }
export interface RefreshOptions { export interface AsyncDataExecuteOptions {
_initial?: boolean _initial?: boolean
} }
export interface _AsyncData<DataT, ErrorT> { export interface _AsyncData<DataT, ErrorT> {
data: Ref<DataT | null> data: Ref<DataT | null>
pending: Ref<boolean> pending: Ref<boolean>
refresh: (opts?: RefreshOptions) => Promise<void> refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
error: Ref<ErrorT | null> error: Ref<ErrorT | null>
} }
@ -94,6 +96,7 @@ export function useAsyncData<
} }
options.lazy = options.lazy ?? (options as any).defer ?? false options.lazy = options.lazy ?? (options as any).defer ?? false
options.initialCache = options.initialCache ?? true options.initialCache = options.initialCache ?? true
options.immediate = options.immediate ?? true
// Setup nuxt instance payload // Setup nuxt instance payload
const nuxt = useNuxtApp() const nuxt = useNuxtApp()
@ -111,7 +114,7 @@ export function useAsyncData<
// TODO: Else, Soemhow check for confliciting keys with different defaults or fetcher // TODO: Else, Soemhow check for confliciting keys with different defaults or fetcher
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT, DataE> const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT, DataE>
asyncData.refresh = (opts = {}) => { asyncData.refresh = asyncData.execute = (opts = {}) => {
// Avoid fetching same key more than once at a time // Avoid fetching same key more than once at a time
if (nuxt._asyncDataPromises[key]) { if (nuxt._asyncDataPromises[key]) {
return nuxt._asyncDataPromises[key] return nuxt._asyncDataPromises[key]
@ -160,7 +163,7 @@ export function useAsyncData<
const fetchOnServer = options.server !== false && nuxt.payload.serverRendered const fetchOnServer = options.server !== false && nuxt.payload.serverRendered
// Server side // Server side
if (process.server && fetchOnServer) { if (process.server && fetchOnServer && options.immediate) {
const promise = initialFetch() const promise = initialFetch()
onServerPrefetch(() => promise) onServerPrefetch(() => promise)
} }
@ -184,11 +187,11 @@ export function useAsyncData<
if (fetchOnServer && nuxt.isHydrating && key in nuxt.payload.data) { if (fetchOnServer && nuxt.isHydrating && key in nuxt.payload.data) {
// 1. Hydration (server: true): no fetch // 1. Hydration (server: true): no fetch
asyncData.pending.value = false asyncData.pending.value = false
} else if (instance && ((nuxt.payload.serverRendered && nuxt.isHydrating) || options.lazy)) { } else if (instance && ((nuxt.payload.serverRendered && nuxt.isHydrating) || options.lazy) && options.immediate) {
// 2. Initial load (server: false): fetch on mounted // 2. Initial load (server: false): fetch on mounted
// 3. Initial load or navigation (lazy: true): fetch on mounted // 3. Initial load or navigation (lazy: true): fetch on mounted
instance._nuxtOnBeforeMountCbs.push(initialFetch) instance._nuxtOnBeforeMountCbs.push(initialFetch)
} else { } else if (options.immediate) {
// 4. Navigation (lazy: false) - or plugin usage: await fetch // 4. Navigation (lazy: false) - or plugin usage: await fetch
initialFetch() initialFetch()
} }

View File

@ -0,0 +1,36 @@
<template>
<div>
Single
<div>
data: {{ data }}
</div>
</div>
</template>
<script setup lang="ts">
const called = ref(0)
const { data, execute } = await useAsyncData(() => Promise.resolve(++called.value), { immediate: false })
if (called.value !== 0) {
throw new Error('Handled should have not been called')
}
if (process.server && data.value !== null) {
throw new Error('Initial data should be null: ' + data.value)
}
await execute()
await execute()
if (process.server && called.value as number !== 2) {
throw new Error('Should have been called once after execute (server) but called ' + called.value + ' times')
}
if (process.client && called.value as number !== 2) {
throw new Error('Should have been called once after execute (client) but called ' + called.value + ' times')
}
if (data.value !== 2) {
throw new Error('Data should be 1 after execute')
}
</script>