JAVASCRIPT

Creating a Reusable Data Fetching Composable with Loading and Error States

Learn to build a robust Vue 3 composable for abstracting data fetching logic, including reactive loading and error states, for cleaner components and better reusability across your application.

import { ref, watchEffect } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(true);

  watchEffect(async (onInvalidate) => {
    loading.value = true;
    error.value = null;
    data.value = null;

    let isCurrentRequest = true;
    onInvalidate(() => {
      isCurrentRequest = false;
    });

    try {
      const response = await fetch(url.value || url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      if (isCurrentRequest) {
        data.value = result;
      }
    } catch (e) {
      if (isCurrentRequest) {
        error.value = e;
      }
    } finally {
      if (isCurrentRequest) {
        loading.value = false;
      }
    }
  });

  return { data, error, loading };
}

// Usage in a Vue component:
// <template>
//   <div v-if="loading">Loading...</div>
//   <div v-else-if="error">Error: {{ error.message }}</div>
//   <div v-else>{{ data }}</div>
// </template>

// <script setup>
// import { useFetch } from './useFetch';
// import { ref, computed } from 'vue';

// const postId = ref(1);
// const url = computed(() => `https://jsonplaceholder.typicode.com/posts/${postId.value}`);
// const { data, error, loading } = useFetch(url);

// // You can also pass a static URL:
// // const { data, error, loading } = useFetch('https://jsonplaceholder.typicode.com/posts/1');

// // Example of updating postId to trigger re-fetch
// const fetchNextPost = () => { postId.value++; };
// </script>
How it works: This snippet demonstrates a custom Vue 3 composable, `useFetch`, for encapsulating asynchronous data fetching logic. It leverages `ref` for reactive state (data, loading, error) and `watchEffect` to automatically re-run the fetch operation whenever the provided URL changes. The `onInvalidate` callback handles race conditions by ensuring only the latest request's result updates the state, preventing stale data issues common in async operations. This pattern promotes code reuse and separation of concerns, making components cleaner.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs