onError Callback
The onError callback is called when the HTTP request fails with a 4xx/5xx status code or a network error occurs.
Signature
typescript
onError?: (error: ApiError) => void | Promise<void>
interface ApiError extends Error {
status: number // HTTP status code (404, 500, etc.)
statusText: string // Status text ("Not Found", "Internal Server Error")
data: any // Error response body
url: string // Request URL
message: string // Error message
}Basic Usage
typescript
useFetchGetPetById(
{ petId: 123 },
{
onError: (error) => {
console.error('Failed to load pet:', error.message)
}
}
)Common Use Cases
Show Error Message
typescript
useFetchGetPets({}, {
onError: (error) => {
showToast(`Error: ${error.message}`, 'error')
}
})Handle by Status Code
typescript
useFetchGetPetById(
{ petId: 123 },
{
onError: (error) => {
switch (error.status) {
case 404:
showToast('Pet not found', 'error')
navigateTo('/pets')
break
case 401:
showToast('Please log in', 'error')
navigateTo('/login')
break
case 403:
showToast('Access denied', 'error')
break
case 500:
showToast('Server error, please try again', 'error')
break
default:
showToast('An error occurred', 'error')
}
}
}
)Redirect on Unauthorized
typescript
useFetchGetPets({}, {
onError: (error) => {
if (error.status === 401) {
// Clear auth token
useCookie('auth-token').value = null
// Redirect to login
navigateTo('/login')
}
}
})Log Errors
typescript
useFetchGetPets({}, {
onError: (error) => {
console.error('[API Error]', {
url: error.url,
status: error.status,
message: error.message,
data: error.data,
timestamp: new Date().toISOString()
})
}
})Retry Logic
vue
<script setup lang="ts">
const retryCount = ref(0)
const maxRetries = 3
const { refresh } = useFetchGetPets({}, {
onError: async (error) => {
if (error.status >= 500 && retryCount.value < maxRetries) {
retryCount.value++
showToast(`Retrying... (${retryCount.value}/${maxRetries})`, 'info')
// Wait 2 seconds and retry
await new Promise(resolve => setTimeout(resolve, 2000))
refresh()
} else {
showToast('Failed after retries', 'error')
}
}
})
</script>Track Error Analytics
typescript
useFetchGetPets({}, {
onError: async (error) => {
await trackEvent('api_error', {
url: error.url,
status: error.status,
message: error.message,
timestamp: Date.now()
})
}
})Show Form Validation Errors
vue
<script setup lang="ts">
const formErrors = ref<Record<string, string>>({})
const { execute: submit } = useFetchCreatePet(
{ body: formData.value },
{
immediate: false,
onError: (error) => {
if (error.status === 422) {
// Validation errors
formErrors.value = error.data.errors || {}
showToast('Please fix validation errors', 'error')
} else {
showToast('Failed to create pet', 'error')
}
}
}
)
</script>
<template>
<form @submit.prevent="submit">
<div>
<input v-model="formData.name" />
<span v-if="formErrors.name" class="error">
{{ formErrors.name }}
</span>
</div>
</form>
</template>When It Runs
onError runs when:
- ✅ Response status is 4xx (400, 401, 403, 404, 422, etc.)
- ✅ Response status is 5xx (500, 502, 503, etc.)
- ✅ Network error (no internet, CORS, timeout)
- ✅ Request was aborted
onError does not run when:
- ❌ Response status is 2xx (use
onSuccess) - ❌ Request hasn't been executed yet
Error Types
Client Errors (4xx)
typescript
useFetchGetPets({}, {
onError: (error) => {
if (error.status >= 400 && error.status < 500) {
// Client error
if (error.status === 400) {
showToast('Bad request', 'error')
} else if (error.status === 401) {
navigateTo('/login')
} else if (error.status === 404) {
showToast('Not found', 'error')
} else if (error.status === 422) {
showToast('Validation failed', 'error')
}
}
}
})Server Errors (5xx)
typescript
useFetchGetPets({}, {
onError: (error) => {
if (error.status >= 500) {
// Server error
showToast('Server error, please try again later', 'error')
// Log for monitoring
logErrorToService(error)
}
}
})Network Errors
typescript
useFetchGetPets({}, {
onError: (error) => {
if (!error.status) {
// Network error (no status code)
showToast('Network error, check your connection', 'error')
}
}
})Async Operations
The callback can be async:
typescript
useFetchGetPets({}, {
onError: async (error) => {
// Send error to monitoring service
await sendToErrorTracking({
url: error.url,
status: error.status,
message: error.message,
stack: error.stack
})
// Show user message
showToast('An error occurred', 'error')
}
})Accessing Error Details
typescript
useFetchGetPets({}, {
onError: (error) => {
console.log('Status:', error.status) // 404
console.log('Status Text:', error.statusText) // "Not Found"
console.log('URL:', error.url) // "/api/pets"
console.log('Message:', error.message) // "Not Found"
console.log('Data:', error.data) // Response body
}
})Complex Examples
Multi-Step Error Handling
typescript
useFetchCreateOrder(
{ body: orderData.value },
{
onError: async (error) => {
// 1. Log error
console.error('[Order Error]', error)
// 2. Track analytics
await trackEvent('order_error', {
status: error.status,
message: error.message
})
// 3. Handle by status
if (error.status === 422) {
// Validation error
showToast('Please check your order details', 'error')
} else if (error.status === 402) {
// Payment required
showToast('Payment failed', 'error')
navigateTo('/checkout/payment')
} else {
// Generic error
showToast('Failed to create order', 'error')
}
}
}
)Conditional Retry
vue
<script setup lang="ts">
const shouldRetry = (error: ApiError) => {
// Retry on 5xx errors or specific 4xx errors
return error.status >= 500 || error.status === 408 || error.status === 429
}
const retries = ref(0)
const maxRetries = 3
const { refresh } = useFetchGetPets({}, {
onError: async (error) => {
if (shouldRetry(error) && retries.value < maxRetries) {
retries.value++
// Exponential backoff
const delay = Math.pow(2, retries.value) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
refresh()
} else {
showToast('Request failed', 'error')
retries.value = 0
}
}
})
</script>Error State Management
vue
<script setup lang="ts">
const errorState = ref<{
hasError: boolean
message: string
status: number | null
canRetry: boolean
}>({
hasError: false,
message: '',
status: null,
canRetry: false
})
const { refresh } = useFetchGetPets({}, {
onRequest: () => {
errorState.value = {
hasError: false,
message: '',
status: null,
canRetry: false
}
},
onError: (error) => {
errorState.value = {
hasError: true,
message: error.message,
status: error.status,
canRetry: error.status >= 500
}
}
})
const retry = () => {
if (errorState.value.canRetry) {
refresh()
}
}
</script>
<template>
<div v-if="errorState.hasError" class="error-state">
<p>{{ errorState.message }}</p>
<button v-if="errorState.canRetry" @click="retry">
Retry
</button>
</div>
</template>Best Practices
✅ Do
typescript
// ✅ Show user-friendly messages
onError: (error) => {
if (error.status === 404) {
showToast('Item not found', 'error')
}
}
// ✅ Handle auth errors
onError: (error) => {
if (error.status === 401) {
navigateTo('/login')
}
}
// ✅ Log for debugging
onError: (error) => {
console.error('[API Error]', error)
}
// ✅ Track errors
onError: async (error) => {
await trackEvent('api_error', { status: error.status })
}❌ Don't
typescript
// ❌ Don't ignore errors
onError: () => {
// Nothing - user doesn't know it failed!
}
// ❌ Don't show technical details to users
onError: (error) => {
alert(JSON.stringify(error)) // Too technical
}
// ❌ Don't retry indefinitely
onError: async (error) => {
await new Promise(resolve => setTimeout(resolve, 1000))
refresh() // Will retry forever!
}