Custom Hooks

Custom Hooks are the crown jewel of React’s composition model. They allow you to extract component logic into reusable functions. If you find yourself copying and pasting useEffect code between components, you likely need a Custom Hook.

The Rules of Custom Hooks

  1. Naming: Must start with use (e.g., useWindowSize). This tells React to check for violations of the Rules of Hooks.
  2. Usage: Can call other hooks (useState, useEffect, etc.).
  3. Independence: Two components using the same hook do NOT share state. Each gets its own isolated state instance.

Pattern 1: Logic Extraction (Before vs After)

Imagine you have a component that tracks the window size.

Before: Bloated Component

function App() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return <div>Width: {windowSize.width}px</div>;
}

After: Clean Component with useWindowSize

We extract the state and effect into a separate function.

// The Custom Hook
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

// The Component (Now Trivial)
function App() {
  const size = useWindowSize();
  return <div>Width: {size.width}px</div>;
}

Pattern 2: Synchronizing with Browser APIs

A classic use case is syncing state with browser APIs like localStorage or window.

useLocalStorage

function useLocalStorage(key, initialValue) {
  // 1. Initialize state from local storage or default
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  // 2. Return a wrapped version of useState's setter function
  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
}

Interactive: Hook Composition Demo

See how multiple small hooks combine to power a complex UI. We will compose a simulated useMousePosition hook and a useWindowSize hook to create a responsive, interactive tracking component.

Active Hooks

const { x, y } = useMousePosition();
const { width, height } = useWindowSize();
// Combined Result
return <div>   X: {x}, Y: {y}   Window: {width}x{height} </div>
Mouse: 0, 0
Window: 0 x 0
Move mouse over this box!

Visualizing Logic Extraction

This diagram shows how we move logic from the Component into a Custom Hook.

graph LR subgraph Component ["Component (Before)"] A[State: user] B[Effect: Fetch API] C[Render: User Name] A --- B B --- C end subgraph Refactor ["Refactoring"] D[Extract State & Effect] --> E((useUser)) end subgraph CleanComponent ["Component (After)"] F[Call: useUser()] G[Render: User Name] F --- G end style Component fill:var(--accent-main),stroke:#fff,color:#fff,fill-opacity:0.2 style CleanComponent fill:var(--green-500),stroke:#fff,color:#fff,fill-opacity:0.2 style E fill:var(--purple-500),stroke:#fff,color:#000

Key Takeaways

  • DRY (Don’t Repeat Yourself): If you copy-paste logic, make a hook.
  • Encapsulation: Hide complex implementation details (like AbortController) inside the hook.
  • Composition: Hooks can use other hooks, allowing you to build complex logic from simple blocks.