useFetch React Hook
Custom React hook to fetch data with loading and error state management.
Fetching data in React often leads to the same boilerplate: track loading state, handle errors, and cancel requests on unmount. A custom useFetch
hook encapsulates that logic so your components stay focused on rendering. It takes a URL and dependency list, returning data
, error
, and loading
values.
import { useEffect, useRef, useState } from "react";
export function useFetch<T>(url: string, deps: unknown[] = []) {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(false);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(null);
fetch(url, { signal: controller.signal })
.then(async (res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = (await res.json()) as T;
setData(json);
})
.catch((err) => {
if (err.name !== "AbortError") setError(err);
})
.finally(() => setLoading(false));
return () => controller.abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, ...deps]);
return { data, error, loading };
}
Call this hook inside your components: const { data, error, loading } = useFetch<User[]>('/api/users', [userId]);
. It automatically cancels in‑flight requests if dependencies change or the component unmounts, preventing memory leaks and race conditions. You can extend this pattern to support retries, caching, or custom headers.