Hooks
No hooks found in any category.
useFetch
networkInstallation
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
Name | Type | Default | Description |
---|---|---|---|
url? | string | - | The URL to fetch from |
options? | UseFetchOptions | {} | Fetch options including immediate execution, callbacks, and RequestInit options |
Return Type
UseFetchReturn<T>
Property | Type | Description |
---|---|---|
data | T | null | The fetched data, null if no data or error occurred |
loading | boolean | True when a request is in progress |
error | Error | null | Error 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 | () => void | Abort the current request |
reset | () => void | Reset 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