DEV Community

Cover image for Patterns That Remain Powerful (or Got Stronger) After React Hooks
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Patterns That Remain Powerful (or Got Stronger) After React Hooks

React 16.8’s Hooks API didn’t kill design patterns—it just reshuffled which ones solve today’s problems best.

Below is a field‑guide to the patterns that still earn their keep (or got a turbo‑boost) in a post‑Hooks code‑base.

For each pattern you’ll find:

  • How it works in plain language
  • Why it mattered before Hooks
  • Why it’s still (or newly) useful after Hooks
  • When to avoid it

1. Provider Pattern

How it works

Wrap parts of your tree in a <SomeContext.Provider> and read the value with useContext. Everything nested beneath the provider gets instant read‑only access to the supplied value.

Why it mattered pre‑Hooks

Avoided tedious prop‑drilling in class components by exposing global state (theme, locale, auth).

Why it’s still great

Hooks multiply its reach: pair a Provider with custom hooks (useAuth, useTheme) and you’ve got an ergonomic, type‑safe service locator. You can even compose providers with React 18’s useContextSelector for perf wins.

When not to use

If only one or two components need the data, context adds indirection. Also avoid for high‑frequency, rapidly changing values (e.g. mouse position) unless memoised—re‑renders may cascade.


2. Singleton Pattern

How it works

A module or class guarantees a single shared instance (e.g. AnalyticsService.getInstance()).

Why it mattered pre‑Hooks

Provided a central switchboard for expensive resources: WebSocket, database connection, cache.

Why it’s still great

Hooks make it trivial to consume singletons (via useRef or context) without turning everything into a class. A single in‑memory instance pairs beautifully with React Server Components where statelessness otherwise rules.

When not to use

If your app needs multiple instances per tenant/user, or SSR must create fresh state per request—reach for factories instead.


3. Factory Pattern

How it works

Encapsulates object creation behind a function so you can vary what gets returned at runtime.

Why it mattered pre‑Hooks

Generated configurable services (e.g. makeApiClient(env)).

Why it’s still great

Hooks love declarative factories: use a factory inside useMemo to spin up API clients keyed by auth token, or generate theme objects on demand.

When not to use

When a simple constructor or object literal suffices; factories add ceremony.


4. Flyweight Pattern

How it works

Splits immutable, shareable intrinsic data from extrinsic per‑instance data so thousands of items reuse the same payload (think icons in a list).

Why it mattered pre‑Hooks

Cut memory pressure in heavy DOM trees (virtualised tables, canvases).

Why it’s still great

React 18’s concurrent rendering can mount huge component graphs. Pair flyweight data with hooks like useMemo/useDeferredValue to keep RAM and CPU in check.

When not to use

When object count is small or data mutates per instance—complexity > benefit.


5. Module Pattern

How it works

ES modules expose public exports while hoisting private state to the module scope.

Why it mattered pre‑Hooks

Prevented global namespace pollution; enabled lazy code‑splitting.

Why it’s still great

Server Components & Vite’s ES‑module graph make tree‑shaking first‑class. Combined with hooks you can hold module‑level caches (let cache = new Map()) that survive re‑renders but reset between test cases.

When not to use

When you actually need multiple instances in parallel—prefer factories or contexts.


6. Proxy Pattern

How it works

Wrap an object with new Proxy(target, handler) to intercept get/set/apply etc.

Why it mattered pre‑Hooks

Popular for lazy loading and validation in services and ORMs.

Why it’s still great

In a hook you can expose a proxy‑guarded settings object so any component that mutates the object triggers logging, analytics or undo stacks.

When not to use

Perf‑critical hot paths—each trap adds overhead; TypeScript typings get messy.


7. Observer Pattern

How it works

Observers subscribe; the observable notifies on change (RxJS, EventEmitter).

Why it mattered pre‑Hooks

Powered realtime UIs before Redux/Flux matured.

Why it’s still great

Custom hooks like useObservable bridge RxJS streams to React. The combination excels at sockets, sensor data, GraphQL subscriptions.

When not to use

For static or infrequently changing data—simple fetch + state is clearer.


8. Mediator / Middleware Pattern

How it works

A central hub (Redux store, Express middleware stack) routes messages between peers, enforcing loose coupling.

Why it mattered pre‑Hooks

Enabled global state, undo/redo, analytics without components knowing each other.

Why it’s still great

Hooks make consumption painless (useSelector, useDispatch), but the architecture remains: you still need a store or event bus in multi‑team monorepos.

When not to use

Tiny apps where direct prop passing or local reducer state is enough.


9. Compound Component Pattern

How it works

A parent component manages shared state and exposes stateless sub‑components that implicitly talk via React Context (e.g. <Tabs>, <Tabs.Tab>, <Tabs.Panel>).

Why it mattered pre‑Hooks

Gave developers “slot‑based” APIs without prop drilling.

Why it’s still great

Hooks let the parent hold state (useState, useReducer) and the children consume it with useContext—no classes, no HOCs.

When not to use

If only one sub‑part ever renders, the pattern over‑abstracts; also be careful with multiple nested compound sets (context clashes).


10. Command Pattern

How it works

Encapsulates an action in an object with execute() and optional undo().

Why it mattered pre‑Hooks

Structured complex UIs (drawing apps, forms) with undo/redo capability.

Why it’s still great

useReducer is a natural command dispatcher; keep a stack in a custom useUndoRedo hook and replay actions in either direction.

When not to use

Simple CRUD apps without history—you’ll incur boilerplate without pay‑off.


11. Prototype Pattern (honourable mention)

How it works

Objects share behaviour through the prototype chain rather than via classes.

Why it mattered pre‑Hooks

Base of every JS object; alleviated memory when duplicating methods.

Why it’s still great

Still the foundation for libraries (Date, Array) your hooks use. Explicit prototypal tricks are rarer now, but understanding the model helps debug performance issues in Proxy/Flyweight code.


Conclusion

React Hooks simplified component logic sharing—but they didn’t obsolete well‑chosen design patterns.

Instead, Hooks:

  • Lower the barrier to consuming patterns (contexts, observers, singletons) in purely functional code.
  • Pair with architectural patterns (mediators, commands, flyweights) to keep large apps fast and maintainable.
  • Encourage composition, so factories feed providers, which expose hooks that talk to proxies… and so on.

Learn the patterns, keep them small and purposeful, and your post‑Hooks code‑base will stay predictable, testable and fast for years to come. ✨


Top comments (0)