Best Practices for Fetching Data in Vue.js
Axios is a popular Javascript library used to make HTTP requests from the browser or Node.js, it is an alternative to Javascripts Fetch API. Why axios? It is widely used in frontend frameworks
Fetching data in any application is a common practice, in order to make our code more efficient and avoid common pitfalls while fetching data we need to follow some of the best practices out there. Here are some of best practices used out there by most Vue.js developers out in the industry.
Adding Axios to you project
Assuming you have already set up your project using you Vue CLI or your preferred method, we are going to add Axios to our project.
npm install axios
Axios is a popular Javascript library used to make HTTP requests from the browser or Node.js, it is an alternative to Javascripts Fetch API. Why axios? It is widely used in frontend frameworks such as Vue, React and Angular because of various reasons, I particularly love it because it has automatic JSON transformation reducing the need to manually parse and stringify data. It also provides consistent error handling and clear error messages making debugging easier.
Abstract Data Fetching to a Separate Service Layer
Separating API requests into service files makes your code more modular, reusable and easy to test. It can then be imported into any component simplifying updates to your API logic across components.
For example, create a userService.js to handle user-related API calls:
// userService.js
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
export default {
getUsers() {
return apiClient.get('/users');
}
};
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
Use Composables for Reusable Logic.
Vue’s composition API allows you to create composable functions. Composable functions arefunctions that encapsulate and organize reusable logic. They are part of the Composition API and allow you to separate concerns by grouping related logic outside of components
Example:
We will create a composable for fetching data, the composable will be responsible for accepting a URL for fetching data from, manage loading and error states, return data so that any component can use it.
// src/composables/useFetch.js
import { ref } from 'vue';
import axios from 'axios';
export function useFetch(url) {
const data = ref(null);
const isLoading = ref(false);
const error = ref(null);
const fetchData = async () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get(url);
data.value = response.data; // Axios automatically parses JSON
} catch (err) {
error.value = err.response ? err.response.data.message : err.message;
} finally {
isLoading.value = false;
}
};
// Automatically fetch data when the composable is used
fetchData();
return {
data,
isLoading,
error,
};
}
Using the composable
<!-- src/components/PostsList.vue -->
<template>
<div>
<h1>Posts</h1>
<div v-if="isLoading">Loading...</div>
<div v-if="error">{{ error }}</div>
<ul v-if="data && data.length">
<li v-for="post in data" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
<script>
import { useFetch } from '@/composables/useFetch';
export default {
name: 'PostsList',
setup() {
const { data, isLoading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');
return {
data,
isLoading,
error,
};
},
};
</script>
Avoid Fetching Same Data in Multiple Components
Fetching data that is same in multiple components is unnecessary and can lead to issues such as redundant network requests potentially overloading the server and leading to increased network traffic. It also leads to inconsistent data states, due to the reason that components may fetch data at different times leading to inconsistent or out-of-sync states across the application.
Some of the work arounds to this are
- Global State Management (Vuex or Pinia)
- A global store, like Vuex or Pinia, centralizes data and makes it accessible to any component, reducing the need to re-fetch it.
- Provide/Inject API
- Use Vue’s provide/inject API to share data across nested components without passing props.
- Caching with Composables
- You can use a composable function that caches data and serves it across multiple components. This approach works well if the data is relatively static.
- Parent-Child Data Flow
- One simple strategy is to fetch data in a parent component and then pass it down to child components via props. This ensures that data is fetched only once and is shared across the necessary components.
Use Lifecycle Hooks Effectively
Mounted Hook: Use the mounted lifecycle hook for fetching data when a component loads.
Example
<template>
<div v-if="isLoading">Loading...</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
posts: [], // To store the fetched posts
isLoading: true, // Loading state
error: null, // Error message if fetching fails
};
},
mounted() {
this.fetchPosts(); // Fetch posts when component mounts
},
methods: {
async fetchPosts() {
this.isLoading = true; // Set loading to true
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
this.posts = response.data; // Store the fetched data
} catch (error) {
this.error = error.message; // Set error message if fetching fails
} finally {
this.isLoading = false; // Set loading to false
}
},
},
};
</script>
Using beforeUnmount for Cleanup
The beforeUnmount hook is used to clean up any ongoing actions when the component is about to be removed from the DOM
Example
<template>
<div v-if="isLoading">Loading...</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
posts: [],
isLoading: true,
cancelTokenSource: null, // To store Axios CancelToken source
};
},
mounted() {
this.fetchPosts(); // Fetch posts when the component mounts
},
beforeUnmount() {
// Cancel ongoing request if it exists
if (this.cancelTokenSource) {
this.cancelTokenSource.cancel('Request canceled by user.'); // Cancel the request
}
},
methods: {
async fetchPosts() {
this.isLoading = true;
this.cancelTokenSource = axios.CancelToken.source(); // Create CancelToken source
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts', {
cancelToken: this.cancelTokenSource.token, // Pass the cancel token
});
this.posts = response.data; // Store the fetched data
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message); // Log cancellation
} else {
this.error = error.message; // Set error message if fetching fails
}
} finally {
this.isLoading = false; // Set loading to false
}
},
},
};
</script>
Implement Caching and Throttling
Caching
This is the process of storing frequently accessed data so that subsequent requests for the same data can be served quickly from memory rather than having to make a new request to the serve. It reduces load times and server load.
Manual Caching with Pinia
// stores/posts.js
import { defineStore } from 'pinia';
import axios from 'axios';
export const usePostsStore = defineStore('posts', {
state: () => ({
posts: [],
isLoading: false,
error: null,
}),
actions: {
async fetchPosts() {
// If posts are already cached, return early
if (this.posts.length) {
return;
}
this.isLoading = true;
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
this.posts = response.data; // Cache the posts in the state
} catch (error) {
this.error = error.message; // Store error message
} finally {
this.isLoading = false; // Set loading to false
}
},
},
});
Using the Store in a Component:
<template>
<div v-if="isLoading">Loading...</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>
<script>
import { usePostsStore } from '@/stores/posts';
export default {
setup() {
const store = usePostsStore();
store.fetchPosts(); // Fetch posts when the component mounts
return {
posts: store.posts,
isLoading: store.isLoading,
};
},
};
</script>
Throttling
Throttling is the limiting of the number of requests to a server at a particular time frame. It is particularly useful when dealing with user input, such as search fields, where you want to limit how often API calls are made as the user types.
Implementing Throttling with Axios
You can implement throttling using a debounce function. Here’s a simple example using a custom debounce function along with Axios to fetch data as the user types in a search input.
Debounce Function:
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
Using Debounce in a Component:
<template>
<input v-model="search" placeholder="Search posts..." />
<ul>
<li v-for="post in filteredPosts" :key="post.id">{{ post.title }}</li>
</ul>
</template>
<script>
import axios from 'axios';
import { ref, watch } from 'vue';
import { usePostsStore } from '@/stores/posts';
export default {
setup() {
const postsStore = usePostsStore();
postsStore.fetchPosts(); // Fetch all posts initially
const search = ref('');
const filteredPosts = ref([]);
// Debounce the search function
const debouncedFetch = debounce(async (query) => {
if (!query) {
filteredPosts.value = postsStore.posts; // Show all posts if no search query
return;
}
// Implement API call to search posts
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts?search=${query}`);
filteredPosts.value = response.data; // Update filtered posts
}, 300); // 300 ms debounce
// Watch for changes in the search input
watch(search, (newValue) => {
debouncedFetch(newValue);
});
return {
search,
filteredPosts,
};
},
};
</script>
Conclusion
The following are some of the best practices while fetching data in Vue, there are a lot more out there, at least this are the once I have tried and tested.
Peace!