In Vue 3 (especially when using the Composition API), fetching data cleanly and reactively is simple once you understand the pattern.
Let’s go through it step-by-step using both fetch() and axios.
🌐 1. Where to Fetch Data?
Usually, you’ll fetch data inside the onMounted() lifecycle hook — this ensures:
- The component is mounted in the DOM
- You can safely update reactive state afterward
import { ref, onMounted } from 'vue'
🟢 2. Example using the Native fetch() API
✅ Example: Users.vue
<template>
<div>
<h2>👥 User List</h2>
<p v-if="loading">Loading...</p>
<p v-if="error" class="error">{{ error }}</p>
<ul v-if="users.length">
<li v-for="user in users" :key="user.id">
{{ user.name }} ({{ user.email }})
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
const error = ref(null)
onMounted(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('Network response was not ok')
users.value = await res.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
})
</script>
<style scoped>
.error {
color: red;
}
</style>
🧠 Explanation:
ref()holds reactive data (users,loading,error)onMounted()triggers the data fetch when the component loads- Errors and loading states are handled gracefully
🔵 3. Example using Axios
Axios provides a cleaner syntax and better error handling than fetch().
You can install it via:
npm install axios
✅ Example: Posts.vue
<template>
<div>
<h2>📰 Posts</h2>
<button @click="loadPosts">Reload</button>
<p v-if="loading">Loading...</p>
<p v-if="error" class="error">{{ error }}</p>
<ul v-if="posts.length">
<li v-for="post in posts" :key="post.id">
<strong>{{ post.title }}</strong><br>
{{ post.body }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const posts = ref([])
const loading = ref(false)
const error = ref(null)
async function loadPosts() {
loading.value = true
error.value = null
try {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts?_limit=5')
posts.value = res.data
} catch (err) {
error.value = err.message || 'Failed to fetch posts'
} finally {
loading.value = false
}
}
onMounted(loadPosts)
</script>
<style scoped>
.error {
color: red;
}
</style>
🧠 Axios Features:
- Automatically parses JSON
- Throws clear errors
- Supports interceptors and global configuration
🧩 4. Handling Data in a Centralized Way
For larger apps, you might:
- Use Pinia or Vuex for global state management.
- Create API service files for reusable HTTP calls.
Example API service (api/users.js)
import axios from 'axios'
const api = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
})
export async function getUsers() {
const res = await api.get('/users')
return res.data
}
Use it in your component:
import { ref, onMounted } from 'vue'
import { getUsers } from '../api/users'
const users = ref([])
onMounted(async () => {
users.value = await getUsers()
})
🧠 5. Best Practices
| Tip | Description |
|---|---|
✅ Use onMounted() | Fetch data after DOM is ready |
⚙️ Use ref() or reactive() | Keep data reactive |
💬 Handle loading & error | Improve UX |
| 🌍 Use API modules | Separate logic from components |
| 🧹 Cancel old requests | When dealing with navigation or reactivity (Axios supports cancellation tokens) |
⚡ 6. Bonus: Using async setup() (Vue 3.3+)
You can even make your setup() function async and await data directly!
✅ Example
<script setup async>
import axios from 'axios'
import { ref } from 'vue'
const posts = ref(await axios.get('https://jsonplaceholder.typicode.com/posts?_limit=3')
.then(res => res.data))
</script>
<template>
<div>
<h3>Posts (Loaded in async setup)</h3>
<ul>
<li v-for="p in posts" :key="p.id">{{ p.title }}</li>
</ul>
</div>
</template>
🧠 Useful for SSR or when you want data ready before rendering.
🚀 Summary
| Method | Pros | Cons |
|---|---|---|
| fetch() | Built-in, no dependency | Must handle JSON + errors manually |
| axios | Clean API, better errors, interceptors | Requires install |
| async setup() | Elegant, fewer hooks | Experimental in older Vue versions |
