All skills
Skillintermediate

Hooks Patterns

```tsx // useApi - Data fetching hook function useApi(url: string) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true);

Claude Code Knowledge Pack7/10/2026

Overview

Hooks Patterns

Custom Hook Pattern

// useApi - Data fetching hook
function useApi(url: string) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, error, loading };
}

useDebounce

function useDebounce(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// Usage
function Search() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) search(debouncedQuery);
  }, [debouncedQuery]);
}

useLocalStorage

function useLocalStorage(key: string, initialValue: T) {
  const [value, setValue] = useState(() => {
    if (typeof window === 'undefined') return initialValue;
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

useMediaQuery

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(() =>
    typeof window !== 'undefined' && window.matchMedia(query).matches
  );

  useEffect(() => {
    const media = window.matchMedia(query);
    const listener = (e: MediaQueryListEvent) => setMatches(e.matches);

    media.addEventListener('change', listener);
    return () => media.removeEventListener('change', listener);
  }, [query]);

  return matches;
}

// Usage
function Layout() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  return isMobile ?  : ;
}

useCallback & useMemo

// useCallback: Memoize functions (for child dependencies)
const handleClick = useCallback((id: string) => {
  setSelected(id);
}, []); // Empty deps = stable reference

// useMemo: Memoize expensive calculations
const sortedItems = useMemo(() =>
  [...items].sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// When to use:
// - useCallback: When passing to memoized children
// - useMemo: When calculation is expensive AND deps rarely change

Effect Cleanup

useEffect(() => {
  const subscription = api.subscribe(handler);

  // Cleanup function
  return () => subscription.unsubscribe();
}, []);

// Async effect pattern
useEffect(() => {
  let cancelled = false;

  async function fetchData() {
    const data = await api.getData();
    if (!cancelled) setData(data);
  }

  fetchData();
  return () => { cancelled = true };
}, []);

Quick Reference

HookPurpose
useStateComponent state
useEffectSide effects, subscriptions
useCallbackMemoize functions
useMemoMemoize values
useRefMutable ref, DOM access
useContextRead context
useReducerComplex state logic
Custom HookUse Case
useDebounceInput delay
useLocalStoragePersistent state
useMediaQueryResponsive logic
useApiData fetching