Back to React
React
easy
mid

Which logic in your components should you extract into custom hooks for reusability?

Extract hooks where logic is duplicated, stateful, or involves effects/subscriptions: data fetching, form handling, debounced values, media queries, local storage sync, intersection observers, pagination, and event listeners. Extract for reuse or clarity — not prematurely.

6 min read·~10 min to think through

Custom hooks are how you share stateful logic without sharing markup. Extract when logic is repeated, involves an effect/subscription, or is complex enough that naming it clarifies the component.

Strong candidates to extract

Data & async

  • useFetch / useQuery-style data loading (or just use React Query).
  • usePagination, useInfiniteScroll.
  • useAsync — wraps a promise with loading/error/data state.

Input & timing

  • useDebounce / useThrottle — debounced value or callback.
  • useForm / useFormField — value, validation, touched/dirty state.

Browser APIs & subscriptions

  • useMediaQuery — responsive logic in JS.
  • useLocalStorage / useSessionStorage — state synced to storage, with the storage event.
  • useIntersectionObserver — lazy loading, infinite scroll, visibility.
  • useEventListener — add/remove listeners with correct cleanup.
  • useOnlineStatus, useWindowSize, useScrollPosition.

UI behavior

  • useToggle, useDisclosure (open/close + handlers).
  • useClickOutside — dismiss popovers/menus.
  • usePrevious — previous value of a prop/state.
  • useHover, useFocusWithin.

App-domain hooks

  • useAuth, useCart, useFeatureFlag — domain logic behind a clean API.

Principles for good hooks

  • Single responsibility — a hook does one thing; name says what.
  • Stable return shape — return memoized values/callbacks so consumers don't re-render needlessly.
  • Handle cleanup — every subscription/listener/timer cleaned up in the effect return.
  • Composable — small hooks compose into bigger ones (useInfiniteScroll built on useIntersectionObserver).
  • Don't return JSX — that's a component, not a hook.

When not to extract

  • Used once and trivial — inline is clearer.
  • Extraction would just relocate complexity without reducing it.
  • The "hook" is really a component (it renders).

Don't pre-build a hooks library; extract the second time you copy-paste, or the moment a component's logic obscures its rendering.

Follow-up questions

  • What makes a custom hook well-designed vs a leaky abstraction?
  • Why should hooks return memoized/stable references?
  • How do you compose smaller hooks into larger ones?
  • When is extracting a hook premature?

Common mistakes

  • Extracting hooks prematurely for single-use trivial logic.
  • Hooks that return new object/function references each render, causing re-renders.
  • Forgetting cleanup for listeners/subscriptions/timers.
  • Building a 'hook' that really should be a component.

Performance considerations

  • Hooks should memoize returned values and callbacks (useMemo/useCallback) so consumers stay stable. A poorly designed hook can spread re-renders. Each hook call is independent state — don't expect cross-component sharing without a store/context.

Edge cases

  • Hooks with dependencies that change identity every render.
  • Hooks used conditionally (violates rules of hooks).
  • SSR — hooks touching window/localStorage need guards.
  • Hooks sharing state vs each call getting its own instance (they get their own).

Real-world examples

  • useDebounce powering a search input across the whole app.
  • useIntersectionObserver shared by lazy images, infinite scroll, and analytics impression tracking.

Senior engineer discussion

Seniors extract along the 'repeated, stateful, or effectful' axis and emphasize hook API quality — single responsibility, stable references, cleanup, composability. They distinguish generic utility hooks from domain hooks (useCart, useAuth) and warn against a speculative hooks library; extraction follows real duplication or clarity needs.

Related questions