What Causes This Warning?
This warning appears when you call useAsyncData or useFetch after the component has already mounted. These composables are designed to be called during the setup phase, not after.
The Problem
<script setup>
const loadData = async () => {
// ❌ Wrong - component is already mounted when button is clicked
const { data } = await useFetch('/api/data')
}
</script>
<template>
<button @click="loadData">Load Data</button>
</template>
The Fix
Use $fetch for on-demand data fetching:
<script setup>
const data = ref(null)
const loading = ref(false)
const loadData = async () => {
loading.value = true
try {
// ✅ Correct - use $fetch for post-mount requests
data.value = await $fetch('/api/data')
} finally {
loading.value = false
}
}
</script>
<template>
<button @click="loadData" :disabled="loading">
{{ loading ? 'Loading...' : 'Load Data' }}
</button>
<div v-if="data">{{ data }}</div>
</template>
Understanding the Difference
useFetch / useAsyncData
- Run during component setup (before mount)
- Participate in SSR (data fetched on server)
- Cached and deduplicated
- Used for initial page data
$fetch
- Can be called anytime
- Client-side only when called post-mount
- Not automatically cached
- Used for on-demand requests
Common Scenarios
Form Submission
<script setup>
const formData = ref({ name: '', email: '' })
const result = ref(null)
const submitForm = async () => {
// ✅ Use $fetch for form submission
result.value = await $fetch('/api/submit', {
method: 'POST',
body: formData.value
})
}
</script>
Pagination / Load More
<script setup>
// Initial data with useFetch
const { data: initialData } = await useFetch('/api/items?page=1')
const items = ref(initialData.value?.items || [])
const page = ref(1)
const loadMore = async () => {
page.value++
// ✅ Use $fetch for subsequent pages
const newData = await $fetch(`/api/items?page=${page.value}`)
items.value.push(...newData.items)
}
</script>
Search / Filter
<script setup>
const searchQuery = ref('')
const results = ref([])
// Debounced search function
const search = useDebounceFn(async () => {
if (!searchQuery.value) {
results.value = []
return
}
// ✅ Use $fetch for search requests
results.value = await $fetch('/api/search', {
params: { q: searchQuery.value }
})
}, 300)
watch(searchQuery, search)
</script>
Refresh Data
<script setup>
// Initial fetch
const { data, refresh } = await useFetch('/api/data')
// For manual refresh, use the refresh function from useFetch
const handleRefresh = () => {
refresh() // ✅ This is the proper way to refresh
}
// Or if you need to fetch with different params
const fetchWithParams = async (newParams) => {
// ✅ Use $fetch for dynamic requests
return await $fetch('/api/data', { params: newParams })
}
</script>
Conditional Fetching
<script setup>
const userId = ref(null)
const userData = ref(null)
// When user selects an ID
const selectUser = async (id) => {
userId.value = id
// ✅ Use $fetch for conditional data loading
userData.value = await $fetch(`/api/users/${id}`)
}
</script>
Error Handling with $fetch
<script setup>
const data = ref(null)
const error = ref(null)
const loadData = async () => {
try {
data.value = await $fetch('/api/data')
error.value = null
} catch (e) {
error.value = e.message
data.value = null
}
}
</script>
When to Use Each
| Scenario | Use |
|---|---|
| Initial page data | useFetch / useAsyncData |
| SSR required | useFetch / useAsyncData |
| Button click fetch | $fetch |
| Form submission | $fetch |
| Pagination | $fetch |
| Search/filter | $fetch |
| WebSocket data | $fetch |
| Refresh existing data | refresh() from useFetch |
Reactive $fetch with watch
If you need reactive fetching like useFetch, combine $fetch with watch:
<script setup>
const userId = ref(1)
const userData = ref(null)
// Watch for changes and fetch
watch(userId, async (newId) => {
userData.value = await $fetch(`/api/users/${newId}`)
}, { immediate: true })
</script>
Or create a composable:
// composables/useReactiveFetch.ts
export function useReactiveFetch<T>(url: Ref<string> | (() => string)) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const fetch = async () => {
loading.value = true
error.value = null
try {
const urlValue = typeof url === 'function' ? url() : url.value
data.value = await $fetch(urlValue)
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
watch(url, fetch, { immediate: true })
return { data, loading, error, refresh: fetch }
}
Quick Reference
// Setup phase (before mount)
const { data } = await useFetch('/api/data') // ✅
// After mount (event handlers, watchers, etc.)
const data = await $fetch('/api/data') // ✅
const { data } = await useFetch('/api/data') // ⚠️ Warning