Hone logo
Hone
Problems

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 useFetch function must be a composable, meaning it can be called within setup() or <script setup> of Vue components.
  • URL and Options: It should accept a URL string and an optional RequestInit object (for headers, method, body, etc.) as arguments.
  • State Management: It must manage and expose the following reactive states:
    • data: To hold the fetched data. Initially null or undefined.
    • loading: A boolean indicating if the request is in progress. Initially true (or false if fetched immediately).
    • error: To store any error encountered during the fetch. Initially null.
  • Automatic Fetching: The fetch operation should ideally be triggered automatically when the composable is used.
  • Type Safety: Leverage TypeScript for strong typing of the data and error properties. The generic type parameter for data should allow users to specify the expected response type.
  • AbortController: Implement support for AbortController to allow cancellation of ongoing requests, preventing memory leaks and race conditions.

Expected Behavior:

  1. When useFetch is called with a URL, it should initiate an HTTP request.
  2. While the request is pending, loading should be true.
  3. If the request is successful, data should be updated with the response, and loading should become false.
  4. If an error occurs during the request, error should be populated with the error object, and loading should become false.
  5. The composable should return an object containing the reactive data, loading, and error states.
  6. 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 loading should start as true or false and justify your choice.
  • Request Cancellation: Ensure that if the component using useFetch is 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 useFetch composable 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 fetch API.

Notes

  • Consider how to handle different HTTP methods (GET, POST, PUT, DELETE, etc.) using the RequestInit object.
  • Think about the initial state of loading and data. Should loading be true from the start, or should the fetch only begin when explicitly triggered (though auto-fetch is preferred for this challenge)?
  • The AbortController is 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.
Loading editor...
typescript