Custom Hooks

⚛️ ReactLesson 12Advanced

Custom hooks extract reusable logic from components into standalone functions. Any function starting with use that calls other hooks is a custom hook. They are the primary pattern for sharing stateful logic in React.

1 Essential Custom Hooks
React — Custom Hooks
// useFetch — generic data fetching
function useFetch(url) {
  const [data, setData]       = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError]     = useState(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelled) { setData(d); setLoading(false); } })
      .catch(e => { if (!cancelled) { setError(e.message); setLoading(false); } });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// useLocalStorage — persist state in localStorage
function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    try { return JSON.parse(localStorage.getItem(key)) ?? initial; }
    catch { return initial; }
  });
  useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
  return [value, setValue];
}

// useDebounce — delay value updates
function useDebounce(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

// Usage
function SearchPage() {
  const [query, setQuery]       = useLocalStorage("search", "");
  const debouncedQuery          = useDebounce(query, 400);
  const { data, loading, error } = useFetch(`/api/search?q=${debouncedQuery}`);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {loading && <Spinner />}
      {error   && <p>Error: {error}</p>}
      {data    && <ResultsList results={data} />}
    </>
  );
}
2 Code Challenge
Challenge: Build a useWindowSize hook that returns { width, height } and updates on window resize. Use it to conditionally render a mobile nav vs. desktop nav.