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 };

Tags

react hooks typescript frontend

Related Snippets

typescript

Generic API Client with TypeScript

typescript

Zod Validation Schema

css

Dark Mode Toggle with CSS

css

Glassmorphism Card Component