typescript
UI
React Custom Hook Patterns
Collection of reusable React hooks: useDebounce, useLocalStorage, useFetch, useIntersectionObserver, and useMediaQuery.
Apex Logic
0 copies
typescript
import { useState, useEffect, useRef, useCallback } from 'react';
// Debounce hook - delays value updates
function useDebounce<T>(value: T, delay: number = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// LocalStorage hook with SSR safety
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] {
const [stored, setStored] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch { return initialValue; }
});
const setValue = useCallback((value: T | ((prev: T) => T)) => {
setStored(prev => {
const next = value instanceof Function ? value(prev) : value;
window.localStorage.setItem(key, JSON.stringify(next));
return next;
});
}, [key]);
return [stored, setValue];
}
// Fetch hook with loading/error states
function useFetch<T>(url: string, options?: RequestInit) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(url, { ...options, signal: controller.signal })
.then(res => { if (!res.ok) throw new Error(res.statusText); return res.json(); })
.then(setData)
.catch(err => { if (err.name !== 'AbortError') setError(err.message); })
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Intersection Observer hook for lazy loading
function useIntersectionObserver(options?: IntersectionObserverInit) {
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
const ref = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(([e]) => setEntry(e), options);
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref.current, options?.threshold, options?.rootMargin]);
return { ref, entry, isVisible: !!entry?.isIntersecting };
}
export { useDebounce, useLocalStorage, useFetch, useIntersectionObserver };