Skip to content

Commit

Permalink
feat: refreshNuxtData function and app:data:refresh hook (nuxt#3929)
Browse files Browse the repository at this point in the history
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
  • Loading branch information
3 people authored Mar 28, 2022
1 parent e534ffe commit 8dd77d7
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 16 deletions.
33 changes: 33 additions & 0 deletions docs/content/3.docs/1.usage/1.data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 26 additions & 9 deletions examples/use-async-data/app.vue
Original file line number Diff line number Diff line change
@@ -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>
Expand Down
19 changes: 19 additions & 0 deletions examples/use-async-data/components/CounterExample.vue
Original file line number Diff line number Diff line change
@@ -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>
9 changes: 9 additions & 0 deletions examples/use-async-data/components/MountainExample.vue
Original file line number Diff line number Diff line change
@@ -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>
2 changes: 1 addition & 1 deletion packages/bridge/src/runtime/composables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
21 changes: 16 additions & 5 deletions packages/nuxt3/src/app/composables/asyncData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt3/src/app/composables/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt3/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt3/src/auto-imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const appPreset = defineUnimportPreset({
imports: [
'useAsyncData',
'useLazyAsyncData',
'refreshNuxtData',
'defineNuxtComponent',
'useNuxtApp',
'defineNuxtPlugin',
Expand Down

0 comments on commit 8dd77d7

Please sign in to comment.