Key Principle
Classical design patterns (Gang of Four, etc.) must be re-evaluated for the modern JavaScript and React ecosystem. Many traditional OOP patterns are either obsolete, replaced by language features (ES modules, Hooks), or need significant adaptation to fit component-based architectures. The book organizes this re-evaluation across three interconnected pillars:
- Design Patterns -- code organization and structural choices (Singleton, Observer, Factory, etc.) tested against React's compositional model.
- Rendering Patterns -- content delivery strategies (CSR, SSR, SSG, iSSG, Islands Architecture) selected by measurable KPIs.
- Performance Patterns -- optimization techniques (code splitting, tree shaking, list virtualization, preload/prefetch) driven by Core Web Vitals.
Pattern selection must be driven by measurable performance impact, not by familiarity or theoretical elegance. The collaboration between a JavaScript educator (Hallie) and a Chrome performance lead (Osmani) embeds two evaluation lenses into every pattern: developer ergonomics and measurable browser performance (Web Vitals).
Why This Matters
Without this re-evaluation lens, developers cargo-cult Gang-of-Four patterns into React codebases, producing class-heavy architectures that fight React's compositional grain -- more abstraction layers, harder-to-trace data flow, larger bundle sizes.
React Hooks are the universal simplifier across all three pillars:
- Design patterns: Hooks replace HOCs, render props, mixins, and the class/function component split with a single compositional mechanism.
- Rendering patterns: React.lazy and Suspense enable progressive and selective hydration.
- Performance patterns: Dynamic imports via lazy loading power code splitting at route and component boundaries.
Component structure decisions (state management via Provider/Context, component splitting) directly affect hydration cost and rendering strategy viability. Each rendering strategy (CSR/SSR/SSG/iSSG/Islands) creates specific loading sequence requirements, and the performance patterns are the implementation layer for those architectural choices.
Good Examples
- Singleton replaced by Object.freeze + ES modules: Same encapsulation guarantee without testing fragility or hidden coupling. The classical
getInstance()pattern is unnecessary when ES modules already provide module-scoped singletons. - HOCs replaced by custom Hooks: HOCs introduce wrapper hell (deeply nested component trees in DevTools), prop name collisions, and opaque data origins. Custom Hooks solve the same cross-cutting reuse problem without adding tree depth or prop ambiguity.
- Container/Presentational split evolved through Hooks: The separation principle endures (data logic vs. rendering), but the container wrapper is superseded by extracting custom Hooks (e.g.,
useLoginForm), moving stateful logic out of the component entirely. - Movies app case study: Optimized from 64% to ~96% Lighthouse score through incremental, measured changes -- demonstrating the three-pillar approach in practice.
- Library audit: Replacing react-slick with react-glider saved 78% bundle size; Font Awesome with @svgr/webpack eliminated massive transitive dependencies.
Counterpoints
- Not all classical patterns are obsolete: Mediator/Middleware (Express.js middleware chains), Observer (RxJS), and Proxy (validation/logging at object boundaries) retain relevance when applied to their native problem domains rather than forced into React component architecture.
- Context API is not a universal solution: Context should be reserved for data "required by many components within an application" -- not as a general prop replacement. When a Context value changes, every consumer re-renders regardless of whether the specific data they use changed. It is a broadcasting mechanism, not a fine-grained state manager.
- Declarative style has friction: When output structure diverges from input structure (e.g., interleaving category headers with data rows), imperative iteration can be more pragmatic than
map/filterpipelines. - Some "optimizations" cause regressions: Native lazy loading and certain scroll libraries have been measured to hurt rather than help performance. Measurement before and after every change is non-negotiable.
Key Quotes (with citations)
- "Design patterns are a fundamental part of software development, as they provide typical solutions to commonly recurring problems in software design. Rather than providing specific pieces of software, design patterns are merely concepts that can be used to handle recurring themes in an optimized way." (Introduction, Learning Patterns, Hallie & Osmani, 2022)
- "Because React is composition-focused, it can perfectly map to the elements of your design system. So, in essence, designing for React actually rewards you for thinking in a modular way." (Overview of React.js, Learning Patterns, Hallie & Osmani, 2022)
- "While a component transforms props into UI, a higher-order component transforms a component into another component." (Higher-order component chapter, Learning Patterns, Hallie & Osmani, 2022)
- "Singletons are classes which can be instantiated once, and can be accessed globally. This single instance can be shared throughout our application, which makes Singletons great for managing global state in an application." (Singleton Pattern, Learning Patterns, Hallie & Osmani, 2022)
- "Including state is a task that you should save for last. It is much better to design everything as stateless as possible, using props and events. This makes components easier to maintain, test, and understand." (React Components chapter, Learning Patterns, Hallie & Osmani, 2022)
Rules of Thumb
- Ask "does a Hook make this unnecessary?" before applying any classical design pattern in a React codebase.
- Design stateless-first, add state last. Build the entire component tree using only props and events before introducing any state. Stateless components are pure functions -- deterministic, testable, SSR-compatible by default.
- Name props from the component's perspective, not the parent's. A prop named
useris reusable; a prop namedauthoris coupled to one context. - Extract a sub-component when either condition holds: the UI fragment appears in multiple places, or it is complex enough to reason about independently.
- Choose rendering strategy by metric priority: CSR for TTI-tolerant apps, SSR for SEO/FCP, SSG/iSSG for content sites, Islands for mostly-static pages with interactive pockets.
- Measure before and after every optimization. Performance intuition is unreliable; only Core Web Vitals data validates a change.
- Audit third-party libraries for bundle impact before adopting them. The cost of a dependency is not just its API surface but its transitive weight.
- Fewer stateful components means fewer re-render triggers. The performance cost of state is not just the update itself but the reconciliation cascade it causes in the subtree.
Related References
- Container/Presentational pattern (Design Patterns section)
- HOC and Render Props patterns (Design Patterns section)
- Hooks pattern -- the central design patterns chapter
- Rendering KPIs and the CSR-to-SSR spectrum (Rendering Patterns section)
- Progressive and Selective Hydration (Rendering Patterns section)
- Islands Architecture (Rendering Patterns section)
- PRPL pattern and code splitting (Performance Patterns section)
- List Virtualization (Performance Patterns section)
- Core Web Vitals case study (Rendering Patterns section)