DEV Community

Cover image for Hooks Pattern — Re‑using Stateful Logic the React Way
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Hooks Pattern — Re‑using Stateful Logic the React Way

Insights & examples inspired by “Learning Patterns” by Lydia Hallie & Addy Osmani

“Hooks let you abstract stateful behaviour and side‑effects to plain functions—freeing components to stay focused on rendering.” — Learning Patterns


1  Why hooks?

React components have always been composable, but sharing stateful logic between them felt clunky:

Higher‑Order Components wrapped your tree in layers of indirection; render‑props littered JSX with inline functions. Hooks solve this by moving the reusable logic out of the markup and into declarative functions that can be imported anywhere.

At their core, hooks are just functions that:

  1. are prefixed with use... so linter & DevTools can identify them;
  2. call other hooks (e.g., useState, useEffect) inside their body;
  3. return data or helpers back to the caller.
// reusable logic → custom hook
export function useDebouncedValue<T>(value: T, delay = 300) {
  const [debounced, setDebounced] = React.useState(value);

  React.useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);      // rule-of-hooks: all dependencies listed

  return debounced;
}
Enter fullscreen mode Exit fullscreen mode

Now any component can stay laser‑focused on rendering:

function SearchBox() {
  const [term, setTerm] = React.useState("");
  const debounced = useDebouncedValue(term, 400);

  React.useEffect(() => {
    // 🔍 hit the API only after the user stops typing…
    fetchResults(debounced);
  }, [debounced]);

  return <input value={term} onChange={e => setTerm(e.target.value)} />;
}
Enter fullscreen mode Exit fullscreen mode

2  Principles the authors emphasise

Principle What it means Why it matters
Encapsulation Keep complex behaviour in a single hook file Components stay readable; duplication disappears
Declarative side‑effects Manage effects through dependencies, not lifecycles Predictable, testable, easy to reason about
Composition over inheritance Chain hooks (useAuthuseUser) Small hooks can be combined like Lego
Naming conventions useX, useSomething Communicates intent; enables static rules such as ESLint‑plugin‑react‑hooks
Predictability Always call hooks in the same order Guarantees React can link state to the right call

These principles allow complex apps (Chrome DevTools, Google Photos, Shopify admin, etc.) to stay maintainable while scaling to hundreds of components.


3  Five real‑life software use cases

All examples below have shipped in production apps I’ve worked on or audited.

# Use Case How a Hook Helps
1 Data‑fetching & caching (dashboards, e‑commerce listings) useProducts() wraps SWR/React Query; handles pagination, error states, revalidation
2 Infinite scrolling feeds (social timelines, news apps) useInfiniteScroll(ref, fetchMore) marries IntersectionObserver with fetch logic
3 Auth & token refresh (SPAs behind login) useAuth() stores JWT in context, auto‑refreshes tokens before expiry, exposes login / logout
4 Responsive UI helpers (design systems) useBreakpoint() tracks viewport width and returns sm / md / lg flags without CSS media queries
5 Accessibility focus‑traps (modals, side‑drawers) useFocusTrap(ref) cycles Tab key within a dialog, restoring focus on close

Other honourable mentions: drag‑and‑drop (useDnD), offline detection (useNetworkStatus), performance mark logging (usePerfMark), internationalisation helpers (useLocale).


4  Pattern anatomy: a template you can reuse

/** useSomething.ts */
export function useSomething(initial: Value) {
  // 1️⃣ State
  const [value, setValue] = React.useState(initial);

  // 2️⃣ Derived helpers
  const toggle = React.useCallback(() => setValue(v => !v), []);

  // 3️⃣ Effects (side‑effects)
  React.useEffect(() => {
    analytics.track("something_changed", { value });
  }, [value]);

  // 4️⃣ API
  return [value, toggle] as const;
}
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: Return the minimal API—often a tuple [state, actions] or an object { state, helpers }—to keep consumers flexible.


5  Testing hooks

Because hooks are just functions, you test them the same way you test any function—except they rely on React context. Use libraries like @testing-library/react-hooks (or RTL’s renderHook) to mount them in isolation:

import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";

it("increments the counter", () => {
  const { result } = renderHook(() => useCounter());

  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});
Enter fullscreen mode Exit fullscreen mode

6  Performance gotchas & tips

Problem Fix
Effect runs too often Check dependency array; memoise callbacks with useCallback
Prop‑drilling through many layers Lift logic into a hook + context provider (useTheme)
Large re‑renders on scroll / resize Debounce or throttle updates inside hook (useWindowScroll)

7  Takeaways

  • Hooks embody the composition philosophy that Lydia & Addy argue for throughout the book.
  • They replace heavyweight patterns with plain functions—readable, testable, shareable.
  • By isolating behaviour you make components a pure description of what should render, not how.

This article summarises personal notes from “Learning Patterns” (Patterns.dev, CC BY‑NC 4.0) and production experience in SaaS dashboards, fintech apps, and PWA e‑commerce storefronts.

Top comments (0)