useFetch
Hook for fetching data from URLs with loading states, error handling, and automatic JSON parsing
About
Hook for fetching data from URLs with loading states, error handling, and automatic JSON parsing
A hook for fetching data from URLs with proper TypeScript generics, error handling, and automatic JSON parsing. It manages loading states and provides a fetch function for manual data fetching.
Note: This hook does not automatically fetch on mount. You must call the returned startFetch function to trigger the request.
Examples
import { useFetch } from "rooks";
type User = { id: number; name: string };
export default function UserCard() {
const { data, error, loading, startFetch } = useFetch<User>("/api/user/1");
return (
<section>
<p>
{loading ? "Loading…" : (error?.message ?? data?.name ?? "Not loaded")}
</p>
<button type="button" onClick={startFetch}>
Load user
</button>
</section>
);
}import { useFetch } from "rooks";
import { useEffect, useMemo } from "react";
function UserProfile({ userId }: { userId: string }) {
const fetchOptions = useMemo(
() => ({
headers: { Authorization: "Bearer token" },
onSuccess: (data: User) => console.log("User loaded:", data),
onError: (error: Error) => console.error("Failed to load user:", error),
onFetch: () => console.log("Fetching user data..."),
}),
[]
);
const {
data: user,
loading,
error,
startFetch,
} = useFetch<User>(
`https://api.example.com/users/${userId}`,
fetchOptions
);
// Fetch data when component mounts
useEffect(() => {
startFetch();
}, [startFetch]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>No user data</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={startFetch}>Refresh</button>
</div>
);
}Advanced Usage
POST Request with Body
function CreateUser() {
const {
data: newUser,
loading,
error,
startFetch,
} = useFetch<User>("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer token",
},
body: JSON.stringify({
name: "John Doe",
email: "john@example.com",
}),
onSuccess: (data) => {
console.log("User created successfully:", data);
// Navigate to user profile or show success message
},
onError: (error) => {
console.error("Failed to create user:", error);
// Show error notification
},
onFetch: () => {
console.log("Creating new user...");
// Show loading indicator
},
});
const handleCreateUser = () => {
startFetch();
};
if (loading) return <div>Creating user...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={handleCreateUser}>Create User</button>
{newUser && (
<div>
<h1>User Created</h1>
<p>Name: {newUser.name}</p>
<p>Email: {newUser.email}</p>
</div>
)}
</div>
);
}DELETE Request
function DeleteUser({ userId }: { userId: string }) {
const {
data: result,
loading,
error,
startFetch,
} = useFetch<{ success: boolean }>(
`https://api.example.com/users/${userId}`,
{
method: "DELETE",
headers: {
Authorization: "Bearer token",
},
onSuccess: (data) => {
if (data.success) {
console.log("User deleted successfully");
// Remove user from UI or navigate away
}
},
onError: (error) => {
console.error("Failed to delete user:", error);
// Show error notification
},
}
);
const handleDeleteUser = () => {
startFetch();
};
if (loading) return <div>Deleting user...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={handleDeleteUser}>Delete User</button>
{result && (
<div>
<h1>User Deleted</h1>
<p>Success: {result.success ? "Yes" : "No"}</p>
</div>
)}
</div>
);
}API Reference
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL to fetch data from |
options | Omit<RequestInit, "signal"> plus the callbacks below | No | Fetch options; the hook owns its abort signal |
Return value
| Property | Type | Description |
|---|---|---|
data | T | null | The fetched data or null |
loading | boolean | Whether the request is loading |
error | Error | null | Error object if request failed |
startFetch | () => Promise<void> | Function to trigger a fetch |
Options
The options object extends the standard RequestInit interface (excluding signal for compatibility):
| Property | Type | Default | Description |
|---|---|---|---|
method | string | 'GET' | HTTP method |
headers | Record<string, string> | {} | Request headers |
body | string | FormData | URLSearchParams | - | Request body |
cache | RequestCache | 'default' | Cache strategy |
credentials | RequestCredentials | 'same-origin' | Credentials policy |
mode | RequestMode | 'cors' | Request mode |
redirect | RequestRedirect | 'follow' | Redirect policy |
referrer | string | - | Referrer URL |
referrerPolicy | ReferrerPolicy | - | Referrer policy |
integrity | string | - | Subresource integrity |
keepalive | boolean | - | Keep-alive flag |
onSuccess | (data: T) => void | - | Success callback |
onError | (error: Error) => void | - | Error callback |
onFetch | () => void | - | Fetch start callback |
Notes
- This hook does not cache requests - each call triggers a fresh fetch
- Starting another request does not cancel earlier calls; overlapping requests may still complete and update state
- Unmounting aborts active requests and prevents callbacks or state updates after unmount
startFetchis bound to the URL and options object from its render; memoizeoptionsbefore usingstartFetchas an effect dependency- The hook does not automatically fetch on mount - you must call the
startFetchfunction - The hook automatically handles JSON parsing of responses
- HTTP errors (4xx, 5xx) are thrown as
Errorobjects withstatusandstatusTextproperties - Network errors are properly caught and exposed through the
errorproperty - The
startFetchfunction returns a Promise that resolves when the request completes - TypeScript generics provide full type safety for the fetched data
- Callbacks are called at appropriate times during the fetch lifecycle:
onFetch: Called when the fetch starts (before the request is made)onSuccess: Called when the request succeeds (with the fetched data)onError: Called when the request fails (with the error object)
Related
usePromise- Handle promises without loading statesuseAsyncEffect- Async operations in effects
Behavior and lifecycle
Requests start only when startFetch is called. Each call gets its own AbortController; calls may overlap, and whichever response finishes later can overwrite an earlier result. loading is a single boolean, so one completed call can clear it while another is still pending. Operation failures are stored and passed to onError rather than rethrown by startFetch. Unmount aborts every active request and prevents all later state updates and callbacks.
Compatibility and accessibility
The hook does not fetch during SSR. It always parses successful bodies as JSON; empty or non-JSON responses therefore become errors. HTTP failures include status and statusText; network, CORS, parsing, and abort failures use their platform errors. Present loading and failure states accessibly, and use a request layer with deduplication/caching when concurrency semantics matter.