Vue.js Error Handling
When building Vue.js applications that communicate with servers, handling errors properly is crucial for creating a robust user experience. In this section, we'll explore different strategies for managing errors that occur during HTTP requests.
Introduction to Error Handling
HTTP requests can fail for various reasons:
- Server is down or unreachable
- Network connectivity issues
- Authentication failures
- Invalid data submissions
- Server-side errors
Proper error handling helps:
- Prevent application crashes
- Provide feedback to users
- Log issues for debugging
- Implement recovery strategies
Basic Error Handling with Axios
Using try/catch with async/await
The most straightforward approach is using JavaScript's try/catch
with async/await
:
async fetchData() {
try {
const response = await axios.get('https://api.example.com/data');
this.data = response.data;
} catch (error) {
console.error('Error fetching data:', error);
this.errorMessage = 'Failed to load data. Please try again later.';
}
}
This pattern is clean and easy to understand, making it ideal for Vue methods.
Using Promise Chains
If you prefer promise chains, you can handle errors with .catch()
:
fetchData() {
axios.get('https://api.example.com/data')
.then(response => {
this.data = response.data;
})
.catch(error => {
console.error('Error fetching data:', error);
this.errorMessage = 'Failed to load data. Please try again later.';
});
}
Understanding Axios Error Objects
Axios provides rich error objects that contain helpful information:
catch (error) {
if (error.response) {
// The server responded with a status code outside the 2xx range
console.log(error.response.data); // Response data from server
console.log(error.response.status); // HTTP status code
console.log(error.response.headers); // HTTP headers
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened while setting up the request
console.log('Error', error.message);
}
}
Creating a Global Error Handler
For consistency, you can create a centralized error handler:
// In a separate file, e.g., errorHandler.js
export default {
handle(error, vm) {
if (error.response) {
switch (error.response.status) {
case 401:
vm.$store.dispatch('auth/logout');
vm.$router.push('/login');
return 'Your session has expired. Please log in again.';
case 403:
return 'You do not have permission to perform this action.';
case 404:
return 'The requested resource was not found.';
case 500:
return 'Server error. Please try again later.';
default:
return 'An error occurred. Please try again.';
}
} else if (error.request) {
return 'Network error. Please check your connection.';
} else {
return error.message;
}
}
}
Then use it in your components:
import ErrorHandler from '@/utils/errorHandler';
export default {
methods: {
async fetchData() {
try {
const response = await axios.get('/api/data');
this.data = response.data;
} catch (error) {
this.errorMessage = ErrorHandler.handle(error, this);
}
}
}
}
Axios Interceptors for Global Error Handling
Axios interceptors allow you to globally handle request or response errors:
// In your main.js or a dedicated axios config file
import axios from 'axios';
import store from './store';
import router from './router';
// Create axios instance
const api = axios.create({
baseURL: 'https://api.example.com'
});
// Add a response interceptor
api.interceptors.response.use(
response => response, // Simply return the response for successful requests
error => {
// Handle 401 Unauthorized globally
if (error.response && error.response.status === 401) {
store.dispatch('auth/logout');
router.push('/login');
}
// Add global error logging
console.error('API Error:', error);
// Pass the error to the calling function
return Promise.reject(error);
}
);
export default api;
User-Friendly Error Messages
Use Vue's reactivity to display error messages to users:
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-if="error" class="error-message">
{{ error }}
</div>
<div v-if="!loading && !error">
<!-- Content here -->
<ul>
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
</div>
<button @click="retry" v-if="error">Retry</button>
</div>
</template>
<script>
export default {
data() {
return {
data: [],
loading: false,
error: null
}
},
methods: {
async fetchData() {
this.loading = true;
this.error = null;
try {
const response = await this.$axios.get('/api/items');
this.data = response.data;
} catch (error) {
if (error.response && error.response.status === 404) {
this.error = 'We couldn\'t find what you were looking for.';
} else {
this.error = 'Something went wrong. Please try again later.';
}
} finally {
this.loading = false;
}
},
retry() {
this.fetchData();
}
},
mounted() {
this.fetchData();
}
}
</script>
Form Submission Error Handling
When submitting forms, handle validation errors from the server:
<template>
<form @submit.prevent="submitForm">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
id="username"
v-model="form.username"
class="form-control"
/>
<div v-if="errors.username" class="error">{{ errors.username }}</div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
v-model="form.email"
class="form-control"
/>
<div v-if="errors.email" class="error">{{ errors.email }}</div>
</div>
<div v-if="generalError" class="general-error">{{ generalError }}</div>
<button type="submit" :disabled="submitting">
{{ submitting ? 'Submitting...' : 'Submit' }}
</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
email: ''
},
errors: {},
generalError: null,
submitting: false
}
},
methods: {
async submitForm() {
this.submitting = true;
this.errors = {};
this.generalError = null;
try {
await this.$axios.post('/api/register', this.form);
// Success! Redirect or show success message
this.$router.push('/success');
} catch (error) {
if (error.response) {
if (error.response.status === 422) {
// Validation errors
this.errors = error.response.data.errors || {};
} else {
// General error
this.generalError = 'An error occurred during submission. Please try again.';
}
} else {
this.generalError = 'Network error. Please check your connection.';
}
} finally {
this.submitting = false;
}
}
}
}
</script>
<style scoped>
.error, .general-error {
color: red;
font-size: 0.9em;
margin-top: 5px;
}
.general-error {
margin-bottom: 15px;
}
</style>
Error Handling Flowchart
Advanced Error Handling Techniques
Retry Logic
Implement retry logic for transient errors:
async fetchWithRetry(url, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
return await this.$axios.get(url);
} catch (error) {
// Only retry for certain errors like network issues or 5xx errors
if (error.request || (error.response && error.response.status >= 500)) {
retries++;
// Exponential backoff
const delay = Math.pow(2, retries) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`Retrying request (${retries}/${maxRetries})...`);
} else {
// Don't retry for 4xx errors, etc.
throw error;
}
}
}
throw new Error(`Failed after ${maxRetries} retries`);
}
Error Boundaries in Vue 3
For Vue 3 applications, you can create error boundaries to catch errors in component trees:
<!-- ErrorBoundary.vue -->
<template>
<slot v-if="!error"></slot>
<div v-else class="error-boundary">
<h2>Something went wrong</h2>
<p>{{ error.message }}</p>
<button @click="resetError">Try again</button>
</div>
</template>
<script>
export default {
data() {
return {
error: null
}
},
errorCaptured(err) {
this.error = err;
return false; // Stop error propagation
},
methods: {
resetError() {
this.error = null;
}
}
}
</script>
Using the error boundary:
<template>
<div>
<h1>My Data Page</h1>
<ErrorBoundary>
<DataComponent />
</ErrorBoundary>
</div>
</template>
Real-World Example: Dashboard with Error Handling
Let's build a simple dashboard that handles various error scenarios:
<template>
<div class="dashboard">
<h1>Dashboard</h1>
<!-- Global alert for critical errors -->
<div v-if="criticalError" class="alert alert-danger">
{{ criticalError }}
<button @click="reloadPage" class="btn btn-sm btn-outline-light ml-2">
Reload Page
</button>
</div>
<div class="dashboard-grid">
<!-- User Stats Widget -->
<div class="dashboard-widget">
<h3>User Statistics</h3>
<div v-if="userStats.loading" class="spinner"></div>
<div v-else-if="userStats.error" class="widget-error">
<p>{{ userStats.error }}</p>
<button @click="loadUserStats" class="btn btn-sm btn-primary">
Retry
</button>
</div>
<div v-else class="stats-display">
<div class="stat">
<div class="stat-value">{{ userStats.data.totalUsers || 0 }}</div>
<div class="stat-label">Total Users</div>
</div>
<div class="stat">
<div class="stat-value">{{ userStats.data.activeUsers || 0 }}</div>
<div class="stat-label">Active Users</div>
</div>
</div>
</div>
<!-- Recent Activity Widget -->
<div class="dashboard-widget">
<h3>Recent Activity</h3>
<div v-if="activity.loading" class="spinner"></div>
<div v-else-if="activity.error" class="widget-error">
<p>{{ activity.error }}</p>
<button @click="loadActivity" class="btn btn-sm btn-primary">
Retry
</button>
</div>
<ul v-else class="activity-list">
<li v-for="item in activity.data" :key="item.id" class="activity-item">
{{ item.description }}
<span class="activity-time">{{ formatTime(item.timestamp) }}</span>
</li>
<li v-if="activity.data.length === 0" class="no-data">
No recent activity
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
criticalError: null,
userStats: {
data: {},
loading: true,
error: null
},
activity: {
data: [],
loading: true,
error: null
}
}
},
methods: {
async loadUserStats() {
this.userStats.loading = true;
this.userStats.error = null;
try {
const response = await this.$axios.get('/api/dashboard/user-stats');
this.userStats.data = response.data;
} catch (error) {
console.error('User stats error:', error);
this.userStats.error = 'Could not load user statistics';
// Check for authentication issues
if (error.response && error.response.status === 401) {
this.criticalError = 'Your session has expired. Please log in again.';
setTimeout(() => this.$router.push('/login'), 3000);
}
} finally {
this.userStats.loading = false;
}
},
async loadActivity() {
this.activity.loading = true;
this.activity.error = null;
try {
const response = await this.$axios.get('/api/dashboard/recent-activity');
this.activity.data = response.data;
} catch (error) {
console.error('Activity error:', error);
this.activity.error = 'Could not load recent activity';
} finally {
this.activity.loading = false;
}
},
formatTime(timestamp) {
return new Date(timestamp).toLocaleTimeString();
},
reloadPage() {
window.location.reload();
},
async loadDashboard() {
try {
await Promise.all([
this.loadUserStats(),
this.loadActivity()
]);
} catch (error) {
this.criticalError = 'Dashboard failed to load. Please try again later.';
}
}
},
mounted() {
this.loadDashboard();
}
}
</script>
<style scoped>
/* Styling would go here */
</style>
This example demonstrates several error handling approaches:
- Component-level errors for individual widgets
- Critical errors that affect the entire page
- Retry mechanisms for individual components
- Session handling with authentication errors
Summary
Effective error handling is essential for building robust Vue.js applications. In this section, we've covered:
- Basic error handling with try/catch and promises
- Understanding Axios error objects
- Creating centralized error handlers
- Using Axios interceptors for global error handling
- Displaying user-friendly error messages
- Handling form validation errors
- Implementing retry logic
- Creating error boundaries in Vue 3
- Building a real-world example with comprehensive error handling
By implementing these strategies, you'll create more resilient applications that gracefully handle errors and provide a better user experience.
Additional Resources
Exercises
- Create a simple Vue component that fetches data from the JSONPlaceholder API and handles possible errors.
- Implement an Axios interceptor that shows a notification for different types of errors.
- Build a form with client-side and server-side validation error handling.
- Create a retry mechanism with exponential backoff for API requests.
- Implement an error boundary component for your Vue application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)