Modularizing React Applications with Established UI Patterns
React should serve as the view layer—not the home for networking, state orchestration, data transformation, and business rules. Applying presentation-domain-data layering makes frontend code easier to test, evolve, reuse, and potentially migrate to another UI technology.
Modularizing React Applications with Established UI Patterns
Author: Juntao QIU | 邱俊涛 | Published: 2023-02-16 | Generated: 2025-02-13 | Domain: martinfowler.com
Tags: ‘#react’ ‘#frontend-architecture’ ‘#layered-architecture’ ‘#refactoring’ ‘#design-patterns’
TLDR
React is a UI library, so components and hooks should not become catch-all containers for rendering, data fetching, state management, API transformation, and domain logic. The article demonstrates progressively refactoring a payment feature into thin presentational components, stateful hooks, domain models and strategies, and a network gateway. This presentation-domain-data layering reduces cognitive load and shotgun surgery while making business logic independently testable and reusable beyond React.
Key Takeaways
- Separate view from non-view logic: Keep components focused on rendering and user interaction; move state coordination into hooks and isolate domain calculations, transformations, and external-system access elsewhere.
- Evolve deliberately as complexity grows: A single component can be split into focused components, reusable hooks, domain objects, and ultimately presentation, domain, and data layers rather than retaining an “everything in component” structure.
- Encapsulate business rules in domain models: The
PaymentMethodmodel owns labels and default-selection behavior, preventing UI-specific conditionals such as checking whether the provider is"cash"from leaking across views. - Use polymorphism to prevent shotgun surgery: Country-specific donation rounding and currency formatting are captured in a
PaymentStrategy/CountryPaymentabstraction, replacing scattered country-code branches in hooks, components, and formatting functions. - Create an API boundary: Extracting payment-method retrieval and conversion into a gateway/anti-corruption layer localizes remote API changes and lets React-focused libraries such as React Query manage network concerns.
Images & Media
- Single Component Application — Initial architecture with a single React component holding the application’s responsibilities.
- Multiple Component Application — View split into several components reflecting the rendered HTML structure.
- State management with hooks — State and side-effect logic extracted from view components into hooks.
- Business models — Domain objects added to centralize mapping, fallback, and business behavior.
- Layered frontend application — Presentation, domain, and data layers applied to a frontend application.
- Payment section — Example payment-method UI used throughout the refactoring walkthrough.
- Refactored Payment with more parts that can be composed easily — Payment feature after extracting models, a hook, and a presentational subcomponent.
- Donate to a charity — Donation round-up requirement added to the payment interface.
- Refactored Payment with donation — Payment feature composition after isolating donation state and UI.
- The shotgun surgery smell — Country-specific conditionals spread across multiple modules.
- Extract class to encapsulate logic — Strategy-based design centralizing country-dependent payment rules.
- More granular split makes the responsibility of each part cleaner — Final structure with views, hooks, models, strategies, and a network client.
Referenced Links
- React Homepage — React’s definition of itself as a JavaScript library for building user interfaces.
- Presentation Domain Data Layering — Martin Fowler’s description of view, domain, and data-layer modularization.
- Extract Function — Refactoring technique used to extract hooks, helpers, and components.
- Replace Conditional with Polymorphism — Refactoring used to replace country-code branching with payment strategies.
- React Query — Suggested library for handling network fetching, retries, caching, and related concerns.
- Gateway — Pattern for encapsulating access to an external service or resource.