NelsonLabs
React Fundamentals/Custom Hooks

Custom Hooks

Custom hooks let you extract reusable stateful logic from components. Any time you find yourself copying the same useState + useEffect pattern across multiple components, that's a custom hook waiting to be written.

A custom hook for fetching data
jsx
import { useState, useEffect } from "react";

// Convention: name starts with 'use'
function useFetch(url) {
  const [data,    setData]    = useState(null);
  const [loading, setLoading] = useState(true);
  const [error,   setError]   = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      try {
        setLoading(true);
        const res  = await fetch(url);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        if (!cancelled) setData(json);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }

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

  return { data, loading, error };
}

// Use it in any component
function CourseList() {
  const { data, loading, error } = useFetch("/api/courses");

  if (loading) return <p>Loading...</p>;
  if (error)   return <p>Error: {error}</p>;

  return (
    <ul>
      {data.map(course => <li key={course.id}>{course.title}</li>)}
    </ul>
  );
}
useLocalStorage — persisting state
jsx
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch {
      return initialValue;
    }
  });

  function set(newValue) {
    setValue(newValue);
    try {
      localStorage.setItem(key, JSON.stringify(newValue));
    } catch {}
  }

  return [value, set];
}

// Use it like useState, but it persists across page reloads
function Settings() {
  const [theme, setTheme] = useLocalStorage("theme", "dark");
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Theme: {theme}
    </button>
  );
}