feat: refreshNuxtData function and app:data:refresh hook (#3929)

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Anthony Fu 2022-03-29 01:12:41 +08:00 committed by GitHub
parent e534ffe22f
commit 8dd77d7b6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 16 deletions

View File

@ -168,6 +168,39 @@ watch(posts, (newPosts) => {
</script>
```
## `refreshNuxtData`
Invalidate the cache of `useAsyncData`, `useLazyAsyncData`, `useFetch` and `useLazyFetch` and trigger the refetch.
This method is useful if you want to refresh all the data fetching for a current page.
### Usage
```ts
refreshNuxtData(keys?: string | string[])
```
Available options:
* `keys`: Provides an array of keys that used in `useAsyncData` to refetch. When it's not specified, all `useAsyncData` and `useFetch` will be refetched.
### Example
```vue
<template>
<div>
{{ pending ? 'Loading' : count }}
</div>
<button @click="refresh">Refresh</button>
</template>
<script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
const refresh = () => refreshNuxtData('count')
</script>
```
## Isomorphic fetch
When we call `fetch` in the browser, user headers like `cookie` will be directly sent to the API. But during server-side-rendering, since the `fetch` request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response.

View File

@ -1,18 +1,35 @@
<script setup>
const ctr = ref(0)
const { data, refresh, pending } = await useAsyncData('/api/hello', () => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })
const showMountain = ref(false)
const refreshing = ref(false)
const refreshAll = async () => {
refreshing.value = true
try {
await refreshNuxtData()
} finally {
refreshing.value = false
}
}
</script>
<template>
<NuxtExampleLayout example="use-async-data" show-tips>
<div>{{ data }}</div>
<div>
<NButton :disabled="pending" @click="refresh">
Refresh Data
</NButton>
<NButton :disabled="pending" @click="ctr++">
+
</NButton>
<div class="flex justify-center gap-2">
<NButton @click="showMountain = !showMountain">
{{ showMountain ? 'Hide' : 'Show' }} Mountain
</NButton>
<NButton :disabled="refreshing" @click="refreshAll">
Refetch All Data
</NButton>
</div>
<div class="flex justify-center gap-2">
<CounterExample />
</div>
<div class="flex justify-center gap-2">
<MountainExample v-if="showMountain" />
</div>
</div>
<template #tips>
<div>

View File

@ -0,0 +1,19 @@
<script setup>
const ctr = ref(0)
const { data, pending, refresh } = await useAsyncData('/api/hello', () => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })
</script>
<template>
<div>
{{ data }}
<div class="flex justify-center gap-2">
<NButton :disabled="pending" @click="ctr++">
+
</NButton>
<NButton :disabled="pending" @click="refresh">
</NButton>
</div>
</div>
</template>

View File

@ -0,0 +1,9 @@
<script setup>
const { data: mountain } = await useFetch(
'https://api.nuxtjs.dev/mountains/mount-everest'
)
</script>
<template>
<pre class="text-sm text-left overflow-auto">{{ mountain }}</pre>
</template>

View File

@ -8,7 +8,7 @@ import { sendRedirect } from 'h3'
import defu from 'defu'
import { useNuxtApp } from './app'
export { useLazyAsyncData } from './asyncData'
export { useLazyAsyncData, refreshNuxtData } from './asyncData'
export { useLazyFetch } from './fetch'
export { useCookie } from './cookie'
export { useRequestHeaders } from './ssr'

View File

@ -137,12 +137,15 @@ export function useAsyncData<
asyncData.refresh()
}
if (options.watch) {
const unwatch = watch(options.watch, () => {
asyncData.refresh()
})
if (instance) {
onUnmounted(() => unwatch())
watch(options.watch, () => asyncData.refresh())
}
const off = nuxt.hook('app:data:refresh', (keys) => {
if (!keys || keys.includes(key)) {
return asyncData.refresh()
}
})
if (instance) {
onUnmounted(off)
}
}
@ -166,6 +169,14 @@ export function useLazyAsyncData<
return useAsyncData(key, handler, { ...options, lazy: true })
}
export function refreshNuxtData (keys?: string | string[]): Promise<void> {
if (process.server) {
return Promise.resolve()
}
const _keys = keys ? Array.isArray(keys) ? keys : [keys] : undefined
return useNuxtApp().callHook('app:data:refresh', _keys)
}
function pick (obj: Record<string, any>, keys: string[]) {
const newObj = {}
for (const key of keys) {

View File

@ -1,5 +1,5 @@
export { defineNuxtComponent } from './component'
export { useAsyncData, useLazyAsyncData } from './asyncData'
export { useAsyncData, useLazyAsyncData, refreshNuxtData } from './asyncData'
export type { AsyncDataOptions, AsyncData } from './asyncData'
export { useHydration } from './hydrate'
export { useState } from './state'

View File

@ -23,6 +23,7 @@ export interface RuntimeNuxtHooks {
'app:suspense:resolve': (Component?: VNode) => HookResult
'app:error': (err: any) => HookResult
'app:error:cleared': (options: { redirect?: string }) => HookResult
'app:data:refresh': (keys?: string[]) => HookResult
'page:start': (Component?: VNode) => HookResult
'page:finish': (Component?: VNode) => HookResult
'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise<NuxtMeta>>) => HookResult

View File

@ -23,6 +23,7 @@ export const appPreset = defineUnimportPreset({
imports: [
'useAsyncData',
'useLazyAsyncData',
'refreshNuxtData',
'defineNuxtComponent',
'useNuxtApp',
'defineNuxtPlugin',