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:
- are prefixed with
use...
so linter & DevTools can identify them; -
call other hooks (e.g.,
useState
,useEffect
) inside their body; - 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;
}
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)} />;
}
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 (useAuth → useUser ) |
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;
}
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);
});
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)