Composable useFetch for Vue 3
This challenge focuses on building a reusable Vue 3 composable function, useFetch, to abstract the logic for making HTTP requests. This is a fundamental pattern for managing asynchronous data fetching in modern Vue applications, improving code organization and reducing boilerplate.
Problem Description
Your task is to create a composable function named useFetch in TypeScript that simplifies making API requests within Vue 3 applications. This composable should handle the fetching process, manage loading and error states, and return the fetched data.
Key Requirements:
- Composable Function: The
useFetchfunction must be a composable, meaning it can be called withinsetup()or<script setup>of Vue components. - URL and Options: It should accept a URL string and an optional
RequestInitobject (for headers, method, body, etc.) as arguments. - State Management: It must manage and expose the following reactive states:
data: To hold the fetched data. Initiallynullorundefined.loading: A boolean indicating if the request is in progress. Initiallytrue(orfalseif fetched immediately).error: To store any error encountered during the fetch. Initiallynull.
- Automatic Fetching: The fetch operation should ideally be triggered automatically when the composable is used.
- Type Safety: Leverage TypeScript for strong typing of the
dataanderrorproperties. The generic type parameter fordatashould allow users to specify the expected response type. - AbortController: Implement support for
AbortControllerto allow cancellation of ongoing requests, preventing memory leaks and race conditions.
Expected Behavior:
- When
useFetchis called with a URL, it should initiate an HTTP request. - While the request is pending,
loadingshould betrue. - If the request is successful,
datashould be updated with the response, andloadingshould becomefalse. - If an error occurs during the request,
errorshould be populated with the error object, andloadingshould becomefalse. - The composable should return an object containing the reactive
data,loading, anderrorstates. - Provide a way to manually re-fetch the data or cancel the current request.
Edge Cases to Consider:
- Invalid URL: How should the composable handle malformed or invalid URLs?
- Network Errors: Implement robust handling for network-related errors (e.g., connection refused, timeout).
- Non-JSON Responses: While the primary focus might be JSON, consider how other response types could be handled (though a generic JSON assumption is acceptable for this challenge).
- Initial Loading State: Decide whether
loadingshould start astrueorfalseand justify your choice. - Request Cancellation: Ensure that if the component using
useFetchis unmounted before the request completes, the request is properly aborted.
Examples
Example 1: Basic Fetch
// In a Vue component:
import { useFetch } from './useFetch'; // Assuming useFetch is in './useFetch.ts'
const { data, loading, error } = useFetch<{ userId: number; id: number; title: string; completed: boolean; }>('https://jsonplaceholder.typicode.com/todos/1');
// In the template:
// <div v-if="loading">Loading...</div>
// <div v-else-if="error">Error: {{ error.message }}</div>
// <div v-else>
// ID: {{ data.id }} - Title: {{ data.title }}
// </div>
Input:
useFetch('https://jsonplaceholder.typicode.com/todos/1')
Output (after successful fetch):
{ data: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }, loading: false, error: null }
Explanation:
The composable makes a GET request to the provided URL. Upon successful completion, the data ref is populated with the JSON response, loading becomes false, and error remains null.
Example 2: Fetch with Options (POST request)
// In a Vue component:
import { useFetch } from './useFetch';
interface PostResponse {
id: number;
title: string;
body: string;
userId: number;
}
const postData = {
title: 'foo',
body: 'bar',
userId: 1,
};
const { data, loading, error } = useFetch<PostResponse>('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
Input:
useFetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', ... })
Output (after successful fetch):
{ data: { id: 101, title: 'foo', body: 'bar', userId: 1 }, loading: false, error: null } (Note: The id might vary based on the API's current state for newly created resources.)
Explanation:
The composable makes a POST request with the specified headers and JSON body. The data ref is updated with the response from the server.
Example 3: Error Handling
// In a Vue component:
import { useFetch } from './useFetch';
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/nonexistent-endpoint');
Input:
useFetch('https://jsonplaceholder.typicode.com/nonexistent-endpoint')
Output (after failed fetch):
{ data: null, loading: false, error: new Error('404 Not Found') } (The exact error object might differ, but it should indicate a failure.)
Explanation:
The request to a non-existent endpoint fails, resulting in an HTTP error. The error ref is populated with an error object, and loading is set to false.
Constraints
- The
useFetchcomposable must be written in TypeScript. - It should be compatible with Vue 3's Composition API.
- The solution should aim for clarity and reusability.
- Use the browser's native
fetchAPI.
Notes
- Consider how to handle different HTTP methods (GET, POST, PUT, DELETE, etc.) using the
RequestInitobject. - Think about the initial state of
loadinganddata. Shouldloadingbetruefrom the start, or should the fetch only begin when explicitly triggered (though auto-fetch is preferred for this challenge)? - The
AbortControlleris crucial for preventing memory leaks in scenarios where components are unmounted before requests complete. - Provide a way to manually re-trigger the fetch or cancel an ongoing one. This might involve returning additional functions from the composable.