typescriptintermediate
React Custom Hook for Data Fetching
A reusable useFetch hook with loading states, error handling, caching, and abort support.
typescriptPress ⌘/Ctrl + Shift + C to copy
import { useState, useEffect, useRef, useCallback } from 'react';
interface UseFetchResult<T> {
data: T | null;
error: string | null;
loading: boolean;
refetch: () => void;
}
const cache = new Map<string, { data: unknown; timestamp: number }>();
const CACHE_TTL = 60_000; // 1 minute
export function useFetch<T>(
url: string,
options?: RequestInit & { cacheTtl?: number }
): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const abortRef = useRef<AbortController | null>(null);
const ttl = options?.cacheTtl ?? CACHE_TTL;
const fetchData = useCallback(async () => {
// Check cache
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < ttl) {
setData(cached.data as T);
setLoading(false);
return;
}
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(null);
try {
const res = await fetch(url, { ...options, signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
cache.set(url, { data: json, timestamp: Date.now() });
setData(json);
} catch (err) {
if ((err as Error).name !== 'AbortError') {
setError((err as Error).message);
}
} finally {
setLoading(false);
}
}, [url, ttl]);
useEffect(() => {
fetchData();
return () => abortRef.current?.abort();
}, [fetchData]);
return { data, error, loading, refetch: fetchData };
}
// Usage
function UserList() {
const { data, error, loading, refetch } = useFetch<{ id: number; name: string }[]>('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error} <button onClick={refetch}>Retry</button></div>;
return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}Use Cases
- Reusable data fetching across components
- Client-side caching with abort on unmount
- Error handling and retry for API calls
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptintermediate
useAsync Hook for Promise Management
Manage async operations with loading, error, and data states using a reusable hook pattern.
Best for: Data fetching with status tracking
#react#hooks
typescriptintermediate
useFetch — Generic Data Fetching Hook
Custom React hook for data fetching with loading, error, and refetch states using the Fetch API.
Best for: API data fetching
#hooks#fetch
typescriptbeginner
usePrevious Hook
Track the previous value of any state or prop using a ref-based hook for comparison logic.
Best for: Detecting value changes
#hooks#state
typescriptintermediate
React Form Validation Hook
A type-safe form validation hook with field-level errors, dirty tracking, and submit handling.
Best for: Type-safe form handling without libraries
#react#forms