Lesson 20 / 25
Fetching Data
Loading, error and data states.
Async composables and query libraries
Every request has at least three states: loading, error and success, and the UI should render all of them. A small composable can hold data, error and loading refs, re-fetch when a reactive input changes (using watchEffect with toValue() on a ref or getter argument), and ignore stale responses when inputs change quickly. For larger apps, TanStack Query for Vue (@tanstack/vue-query) adds caching keyed by a queryKey, deduplication, background refetching and retries through useQuery, plus useMutation for writes. Its option and result names have changed between major versions, so check the docs for your version.
A useFetch composable and its use
Handles all three states.
// composables/useFetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) { // url: string, ref or getter
const data = ref(null)
const error = ref(null)
const loading = ref(false)
watchEffect(async (onCleanup) => {
let cancelled = false
onCleanup(() => { cancelled = true }) // a newer request replaced this one
loading.value = true
error.value = null
try {
const res = await fetch(toValue(url))
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
if (!cancelled) data.value = json
} catch (e) {
if (!cancelled) error.value = e
} finally {
if (!cancelled) loading.value = false
}
})
return { data, error, loading }
}
// <script setup>
// const props = defineProps({ id: String })
// const { data: user, error, loading } = useFetch(() => `/api/users/${props.id}`)
// </script>
// <template>
// <p v-if="loading">Loading...</p>
// <p v-else-if="error">Could not load user: {{ error.message }}</p>
// <UserCard v-else-if="user" :user="user" />
// </template>A restaurant order screen
A good order screen shows 'preparing', 'ready' or 'problem with your order', never a blank board. Your UI should be just as clear about loading, success and error.
Quick check: Why does the composable call toValue(url) inside watchEffect?
- To convert the URL to lowercase
- So the effect tracks a ref or getter argument and refetches when it changes
- To cache the response forever
- Because fetch only accepts refs
Answer
So the effect tracks a ref or getter argument and refetches when it changes — Reading the reactive source inside the effect registers it as a dependency.