Rooks
HooksBrowser APIs

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

ParameterTypeRequiredDescription
urlstringYesThe URL to fetch data from
optionsOmit<RequestInit, "signal"> plus the callbacks belowNoFetch options; the hook owns its abort signal

Return value

PropertyTypeDescription
dataT | nullThe fetched data or null
loadingbooleanWhether the request is loading
errorError | nullError 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):

PropertyTypeDefaultDescription
methodstring'GET'HTTP method
headersRecord<string, string>{}Request headers
bodystring | FormData | URLSearchParams-Request body
cacheRequestCache'default'Cache strategy
credentialsRequestCredentials'same-origin'Credentials policy
modeRequestMode'cors'Request mode
redirectRequestRedirect'follow'Redirect policy
referrerstring-Referrer URL
referrerPolicyReferrerPolicy-Referrer policy
integritystring-Subresource integrity
keepaliveboolean-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
  • startFetch is bound to the URL and options object from its render; memoize options before using startFetch as an effect dependency
  • The hook does not automatically fetch on mount - you must call the startFetch function
  • The hook automatically handles JSON parsing of responses
  • HTTP errors (4xx, 5xx) are thrown as Error objects with status and statusText properties
  • Network errors are properly caught and exposed through the error property
  • The startFetch function 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)

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.

On this page