Skip to content

Custom Hooks

Custom hooks are JavaScript functions that call other hooks. They let you extract component logic into reusable functions — useFetch, useDebounce, useLocalStorage — without changing the component tree.

Extracting Logic into Hooks

If two components share the same state + effect pattern, move it into a function named useSomething. Custom hooks can call useState, useEffect, and other hooks just like components.

Each call to a custom hook gets its own isolated state. Custom hooks share logic, not state — two components calling useCounter() each have separate counts.

function useDebounce(value, delayMs) {
  const [debounced, setDebounced] = useState(value);

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

  return debounced;
}

Debounce hook delays updating a value until typing pauses — ideal for search inputs.

Custom Hook Conventions

// Must start with "use"
function useOnlineStatus() { ... }

// Return whatever API makes sense
return value;
return [value, setValue];
return { data, loading, error };
  • Name must start with use so ESLint can enforce hook rules.
  • Only call custom hooks from components or other custom hooks.
  • Keep hooks focused — one responsibility per hook.
  • Colocate in src/hooks/ for discoverability.

Popular Custom Hook Patterns

Hooks you'll build or install frequently.

Hook Purpose
useDebounce Delay rapid value changes
useLocalStorage Persist state in localStorage
useMediaQuery Responsive breakpoint detection
useOnClickOutside Close dropdown on outside click
useFetch Simple data fetching (prefer TanStack Query)
useToggle Boolean flip helper

Testing Custom Hooks

Use @testing-library/react's renderHook and act to test hooks in isolation without mounting a full component.

const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);

Composing Hooks

Custom hooks can call other custom hooks, building layers of abstraction. Avoid deep chains that hide data flow — two levels is usually enough.

Common Mistakes

  • Naming without use prefix.
  • Returning JSX from custom hooks (that's a component).
  • Sharing mutable state between hook instances incorrectly.
  • Creating hooks for one-liners that add indirection without reuse.

Key Takeaways

  • Custom hooks extract reusable stateful logic.
  • Must start with use and follow rules of hooks.
  • Each hook call has independent state.
  • Test with renderHook from Testing Library.

Pro Tip

Before npm-installing a tiny hook, check if TanStack Query, React Router, or your UI library already provides it.