Home/Learn/React/Custom Hooks

Custom Hooks

Intermediate
Patterns

A custom hook extracts stateful logic so it can be reused and tested. It shares behaviour, never state — every caller gets its own copy.

Overview

Custom hooks are the answer to a question that once needed render props and higher-order components: how do two unrelated components share logic that uses state or effects? A hook is just a function calling other hooks, named with a use prefix so the lint rules can check it. The point most people miss on the first pass is that hooks share code, not values — calling useCounter in two components creates two independent counters. Shared state needs context or a store.

Extracting One

Take the state and effects out of the component; leave the markup behind.

Two hooks you will write in every project
function useDebouncedValue(value, delay = 300) {
  const [debounced, setDebounced] = useState(value)

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

  return debounced
}

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    try { return JSON.parse(localStorage.getItem(key)) ?? initial }
    catch { return initial }
  })

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

  return [value, setValue]
}

// Return an array for a two-value tuple people will rename,
// an object when there are several named things:
return { data, isLoading, error, refetch }

The Rules

Two rules, one reason: hooks are matched by call order.

Position is identity
// 1. Only call hooks at the TOP LEVEL — never in a condition,
//    a loop, or after an early return
// 2. Only call hooks from a component or another hook

function Bad({ isPro }) {
  if (isPro) {
    const [x, setX] = useState(0)      // call order changes between renders
  }
  // React tracks hooks by position, so on the render where isPro
  // flips, every subsequent hook receives the wrong state.
}

function Good({ isPro }) {
  const [x, setX] = useState(0)        // always called
  if (!isPro) return <Upgrade />       // early return AFTER the hooks
}

// The eslint plugin enforces both — never disable it.

// A hook may itself be conditional in what it DOES, just not in
// whether it is called:
useQuery({ queryKey: [...], enabled: isPro })

When to Extract, and What It Does Not Do

The signals for extraction, and the misconception about shared state.

Same logic, separate instances
// Extract when:
//   - the same state + effect pattern appears twice
//   - a component's logic is long enough to hide the markup
//   - the logic deserves its own test
//   - it wraps a browser API (media query, online status, geolocation)

// Do not extract a hook that only wraps one useState — that is
// indirection with no payoff.

// The misconception: hooks do NOT share state.
function A() { const [n, inc] = useCounter() }    // A's counter
function B() { const [n, inc] = useCounter() }    // B's own, separate counter
// Same logic, separate instances. For one shared value, put the
// hook's state in a context provider, or use a store.

// Composition is the real strength — hooks calling hooks:
function useProblemPage(slug) {
  const { data: problem, isLoading } = useProblem(slug)
  const { user } = useAuth()
  const locked = problem?.isPro && !user?.isPro
  useDocumentTitle(problem?.title)
  return { problem, isLoading, locked }
}

Key Points to Remember

  • 1A custom hook is a function starting with "use" that calls other hooks — that prefix is what the lint rules key on
  • 2Hooks are matched by call order, so they must never be called conditionally or inside a loop
  • 3Early returns go after all hook calls, never between them
  • 4Hooks share logic, not state — two callers get two independent copies
  • 5Extract when the pattern repeats or deserves its own test; wrapping a single useState is not worth the indirection

Interview Questions

Sign in to ask Aria
1

Why can hooks not be called conditionally?

Medium
2

If two components use the same custom hook, do they share state?

Medium
3

When is extracting a custom hook not worth it?

Medium

Ask Aria about Custom Hooks

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…