useHooks.iov4.1.2
DocsBlogGitHub
Hooks
No hooks found in any category.

useFetch

network

Installation

npx usehooks-cli@latest add use-fetch

Description

A comprehensive React hook for making HTTP requests using the Fetch API with loading states, error handling, request cancellation, and convenient methods for different HTTP verbs.

Parameters

NameTypeDefaultDescription
url?string-The URL to fetch from
options?UseFetchOptions{}Fetch options including immediate execution, callbacks, and RequestInit options

Return Type

UseFetchReturn<T>
PropertyTypeDescription
dataT | nullThe fetched data, null if no data or error occurred
loadingbooleanTrue when a request is in progress
errorError | nullError object if request failed, null otherwise
execute(url?: string, options?: RequestInit) => Promise<T | null>Manually execute a fetch request with optional URL and options override
abort() => voidAbort the current request
reset() => voidReset the state to initial values and abort any ongoing request

Examples

Basic GET Request

Fetching data with automatic execution

1const { data, loading, error } = useFetch<User[]>('/api/users', { 2 immediate: true 3}); 4 5if (loading) return <div>Loading...</div>; 6if (error) return <div>Error: {error.message}</div>; 7 8return ( 9 <ul> 10 {data?.map(user => ( 11 <li key={user.id}>{user.name}</li> 12 ))} 13 </ul> 14);

Manual Execution with POST

Creating a new resource with manual execution

1const { data, loading, error, execute } = useFetch<User>(); 2 3const createUser = async (userData: CreateUserData) => { 4 try { 5 const newUser = await execute('/api/users', { 6 method: 'POST', 7 headers: { 'Content-Type': 'application/json' }, 8 body: JSON.stringify(userData) 9 }); 10 console.log('User created:', newUser); 11 } catch (err) { 12 console.error('Failed to create user:', err); 13 } 14}; 15 16return ( 17 <form onSubmit={(e) => { 18 e.preventDefault(); 19 createUser(formData); 20 }}> 21 {/* form fields */} 22 <button type="submit" disabled={loading}> 23 {loading ? 'Creating...' : 'Create User'} 24 </button> 25 </form> 26);

Request Cancellation

Cancelling requests and handling cleanup

1const { data, loading, execute, abort, reset } = useFetch<SearchResults>(); 2const [query, setQuery] = useState(''); 3 4const searchUsers = useCallback(async (searchTerm: string) => { 5 if (!searchTerm.trim()) { 6 reset(); 7 return; 8 } 9 10 try { 11 await execute(`/api/search?q=${encodeURIComponent(searchTerm)}`); 12 } catch (error) { 13 if (error.name !== 'AbortError') { 14 console.error('Search failed:', error); 15 } 16 } 17}, [execute, reset]); 18 19// Debounced search 20const debouncedSearch = useDebounce(query, 300); 21useEffect(() => { 22 searchUsers(debouncedSearch); 23}, [debouncedSearch, searchUsers]); 24 25return ( 26 <div> 27 <input 28 value={query} 29 onChange={(e) => setQuery(e.target.value)} 30 placeholder="Search users..." 31 /> 32 <button onClick={abort}>Cancel</button> 33 {loading && <div>Searching...</div>} 34 {data && <SearchResultsList results={data} />} 35 </div> 36);

Dependencies

react

Notes

  • Automatically handles JSON parsing and falls back to text for non-JSON responses
  • Includes request cancellation using AbortController to prevent memory leaks
  • Provides convenience hooks: useGet, usePost, usePut, useDelete
  • Supports both immediate execution and manual triggering
  • Includes onSuccess and onError callbacks for side effects
  • Properly handles cleanup on component unmount

Implementation

1"use client"; 2 3import { useState, useEffect, useCallback, useRef } from "react"; 4 5export interface UseFetchOptions extends RequestInit { 6 immediate?: boolean; 7 onSuccess?: (data: any) => void; 8 onError?: (error: Error) => void; 9} 10 11export interface UseFetchState<T> { 12 data: T | null; 13 loading: boolean; 14 error: Error | null; 15} 16 17export interface UseFetchReturn<T> extends UseFetchState<T> { 18 execute: (url?: string, options?: RequestInit) => Promise<T | null>; 19 abort: () => void; 20 reset: () => void; 21} 22 23export function useFetch<T = any>( 24 url?: string, 25 options: UseFetchOptions = {} 26): UseFetchReturn<T> { 27 const [state, setState] = useState<UseFetchState<T>>({ 28 data: null, 29 loading: false, 30 error: null, 31 }); 32 33 const abortControllerRef = useRef<AbortController | null>(null); 34 const optionsRef = useRef(options); 35 36 // Update options ref when options change 37 useEffect(() => { 38 optionsRef.current = options; 39 }, [options]); 40 41 const execute = useCallback( 42 async ( 43 executeUrl?: string, 44 executeOptions?: RequestInit 45 ): Promise<T | null> => { 46 const targetUrl = executeUrl || url; 47 48 if (!targetUrl) { 49 const error = new Error("No URL provided"); 50 setState((prev) => ({ ...prev, error, loading: false })); 51 optionsRef.current.onError?.(error); 52 throw error; 53 } 54 55 // Abort previous request if it exists 56 if (abortControllerRef.current) { 57 abortControllerRef.current.abort(); 58 } 59 60 // Create new abort controller 61 abortControllerRef.current = new AbortController(); 62 63 setState((prev) => ({ ...prev, loading: true, error: null })); 64 65 try { 66 const { immediate, onSuccess, onError, ...fetchOptions } = 67 optionsRef.current; 68 69 const response = await fetch(targetUrl, { 70 ...fetchOptions, 71 ...executeOptions, 72 signal: abortControllerRef.current.signal, 73 }); 74 75 if (!response.ok) { 76 throw new Error(`HTTP error! status: ${response.status}`); 77 } 78 79 // Try to parse as JSON, fallback to text 80 let data: T; 81 const contentType = response.headers.get("content-type"); 82 83 if (contentType && contentType.includes("application/json")) { 84 data = await response.json(); 85 } else { 86 data = (await response.text()) as T; 87 } 88 89 setState({ data, loading: false, error: null }); 90 onSuccess?.(data); 91 return data; 92 } catch (error) { 93 const fetchError = error as Error; 94 95 // Don't update state if request was aborted 96 if (fetchError.name !== "AbortError") { 97 setState((prev) => ({ ...prev, loading: false, error: fetchError })); 98 optionsRef.current.onError?.(fetchError); 99 } 100 101 throw fetchError; 102 } 103 }, 104 [url] 105 ); 106 107 const abort = useCallback(() => { 108 if (abortControllerRef.current) { 109 abortControllerRef.current.abort(); 110 abortControllerRef.current = null; 111 } 112 }, []); 113 114 const reset = useCallback(() => { 115 abort(); 116 setState({ data: null, loading: false, error: null }); 117 }, [abort]); 118 119 // Execute immediately if immediate option is true and url is provided 120 useEffect(() => { 121 if (options.immediate && url) { 122 execute(); 123 } 124 }, [url, options.immediate, execute]); 125 126 // Cleanup on unmount 127 useEffect(() => { 128 return () => { 129 abort(); 130 }; 131 }, [abort]); 132 133 return { 134 ...state, 135 execute, 136 abort, 137 reset, 138 }; 139} 140 141// Convenience hooks for specific HTTP methods 142export function useGet<T = any>(url?: string, options: UseFetchOptions = {}) { 143 return useFetch<T>(url, { ...options, method: "GET" }); 144} 145 146export function usePost<T = any>(url?: string, options: UseFetchOptions = {}) { 147 return useFetch<T>(url, { ...options, method: "POST" }); 148} 149 150export function usePut<T = any>(url?: string, options: UseFetchOptions = {}) { 151 return useFetch<T>(url, { ...options, method: "PUT" }); 152} 153 154export function useDelete<T = any>( 155 url?: string, 156 options: UseFetchOptions = {} 157) { 158 return useFetch<T>(url, { ...options, method: "DELETE" }); 159} 160