Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

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

Merged
merged 6 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/content/3.api/1.composables/use-async-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type AsyncDataOptions<DataT> = {
pick?: string[]
watch?: WatchSource[]
initialCache?: boolean
immediate?: boolean
}

interface RefreshOptions {
Expand All @@ -32,6 +33,7 @@ interface RefreshOptions {
type AsyncData<DataT, ErrorT> = {
data: Ref<DataT | null>
pending: Ref<boolean>
execute: () => Promise<void>
refresh: (opts?: RefreshOptions) => Promise<void>
error: Ref<ErrorT | null>
}
Expand All @@ -51,14 +53,15 @@ type AsyncData<DataT, ErrorT> = {
* _pick_: only pick specified keys in this array from the `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`)
* _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.

## Return Values

* **data**: the result of the asynchronous function that is passed in
* **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

By default, Nuxt waits until a `refresh` is finished before it can be executed again.
Expand Down
5 changes: 4 additions & 1 deletion docs/content/3.api/1.composables/use-fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type UseFetchOptions = {
baseURL?: string
server?: boolean
lazy?: boolean
immediate?: boolean
default?: () => DataT
transform?: (input: DataT) => DataT
pick?: string[]
Expand All @@ -30,6 +31,7 @@ type AsyncData<DataT> = {
data: Ref<DataT>
pending: Ref<boolean>
refresh: () => Promise<void>
execute: () => Promise<void>
error: Ref<Error | boolean>
}
```
Expand All @@ -51,6 +53,7 @@ type AsyncData<DataT> = {
* `watch`: watch reactive sources to auto-refresh.
* `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.
* `immediate`: When set to `false`, will prevent the request from firing immediately. (defaults to `true`)

::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`.
Expand All @@ -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.
* **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.

By default, Nuxt waits until a `refresh` is finished before it can be executed again.
Expand Down
15 changes: 9 additions & 6 deletions packages/nuxt/src/app/composables/asyncData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ export interface AsyncDataOptions<
pick?: PickKeys
watch?: MultiWatchSources
initialCache?: boolean
immediate?: boolean
}

export interface RefreshOptions {
export interface AsyncDataExecuteOptions {
_initial?: boolean
}

export interface _AsyncData<DataT, ErrorT> {
data: Ref<DataT | null>
pending: Ref<boolean>
refresh: (opts?: RefreshOptions) => Promise<void>
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
error: Ref<ErrorT | null>
}

Expand Down Expand Up @@ -94,6 +96,7 @@ export function useAsyncData<
}
options.lazy = options.lazy ?? (options as any).defer ?? false
options.initialCache = options.initialCache ?? true
options.immediate = options.immediate ?? true

// Setup nuxt instance payload
const nuxt = useNuxtApp()
Expand All @@ -111,7 +114,7 @@ export function useAsyncData<
// TODO: Else, Soemhow check for confliciting keys with different defaults or fetcher
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
if (nuxt._asyncDataPromises[key]) {
return nuxt._asyncDataPromises[key]
Expand Down Expand Up @@ -160,7 +163,7 @@ export function useAsyncData<
const fetchOnServer = options.server !== false && nuxt.payload.serverRendered

// Server side
if (process.server && fetchOnServer) {
if (process.server && fetchOnServer && options.immediate) {
const promise = initialFetch()
onServerPrefetch(() => promise)
}
Expand All @@ -184,11 +187,11 @@ export function useAsyncData<
if (fetchOnServer && nuxt.isHydrating && key in nuxt.payload.data) {
// 1. Hydration (server: true): no fetch
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
// 3. Initial load or navigation (lazy: true): fetch on mounted
instance._nuxtOnBeforeMountCbs.push(initialFetch)
} else {
} else if (options.immediate) {
// 4. Navigation (lazy: false) - or plugin usage: await fetch
initialFetch()
}
Expand Down
36 changes: 36 additions & 0 deletions test/fixtures/basic/pages/useAsyncData/immediate.vue
Original file line number Diff line number Diff line change
@@ -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>