Can Clean Architecture Transform Your Frontend? A Practical React/TypeScript Guide

This article explains how to apply Clean Architecture principles to a frontend React/TypeScript project, covering the three‑layer structure, dependency rules, domain modeling, use‑case design, adapter implementation, code organization, trade‑offs, and practical tips for building a cookie‑store example.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Can Clean Architecture Transform Your Frontend? A Practical React/TypeScript Guide

Clean Architecture Overview

Clean Architecture separates a system into three concentric layers – Domain , Application , and Adapters . Outer layers may depend on inner ones, but inner layers must never depend on outer layers.

Domain Layer

The domain contains the core business entities and pure type definitions. In the cookie‑store example the entities are User, Product, Cart, and Order. Shared‑kernel type aliases keep the domain independent of external libraries.

export type User = {
  id: UniqueId;
  name: UserName;
  email: Email;
  preferences: Ingredient[];
  allergies: Ingredient[];
};

export type Product = {
  id: UniqueId;
  title: ProductTitle;
  price: PriceCents;
  toppings: Ingredient[];
};

export type Cart = {
  products: Product[];
};

export type Order = {
  user: UniqueId;
  cart: Cart;
  created: DateTimeString;
  status: OrderStatus;
  total: PriceCents;
};
Layer diagram: domain in the center, application around it, adapters outside
Layer diagram: domain in the center, application around it, adapters outside

Application Layer

This layer defines use‑cases (application scenarios) and the ports (interfaces) that the core uses to communicate with the outside world.

export type OrderProducts = (user: User, cart: Cart) => Promise<void>;

Key ports: PaymentService

tryPay(amount: PriceCents): Promise<boolean>
NotificationService

notify(message: string): void
OrdersStorageService

orders: Order[]; updateOrders(orders: Order[]): void

Adapter Layer

Adapters translate external APIs into the shapes required by the ports. The example provides a fake payment API, an alert‑based notifier, and a React‑context storage adapter.

export function usePayment(): PaymentService {
  return {
    tryPay: (amount) => fakeApi(true)
  };
}

export function useNotifier(): NotificationService {
  return {
    notify: (msg) => window.alert(msg)
  };
}

export function fakeApi<TResponse>(response: TResponse): Promise<TResponse> {
  return new Promise(res => setTimeout(() => res(response), 450));
}
Adapter diagram: driver and driven adapters
Adapter diagram: driver and driven adapters

Dependency Rule

Domain must not depend on any other layer.

Application may depend on Domain.

Adapters may depend on any layer.

Core Domain Functions

Utility functions operate purely on domain data:

export function hasAllergy(user: User, ingredient: Ingredient): boolean {
  return user.allergies.includes(ingredient);
}

export function hasPreference(user: User, ingredient: Ingredient): boolean {
  return user.preferences.includes(ingredient);
}

export function addProduct(cart: Cart, product: Product): Cart {
  return { ...cart, products: [...cart.products, product] };
}

export function contains(cart: Cart, product: Product): boolean {
  return cart.products.some(p => p.id === product.id);
}

export function totalPrice(products: Product[]): PriceCents {
  return products.reduce((t, { price }) => t + price, 0);
}

export function createOrder(user: User, cart: Cart): Order {
  return {
    user: user.id,
    cart,
    created: new Date().toISOString(),
    status: "new",
    total: totalPrice(cart.products)
  };
}

Checkout Use‑Case Implementation

The checkout scenario validates data, creates an order, attempts payment, notifies on failure, and persists the order on success.

async function orderProducts(user: User, cart: Cart) {
  const order = createOrder(user, cart);
  const paid = await payment.tryPay(order.total);
  if (!paid) return notifier.notify("Payment failed");
  orderStorage.updateOrders([...orderStorage.orders, order]);
  cartStorage.emptyCart();
}

In a real project the use‑case would receive its dependencies via injection rather than pulling them from React hooks, making the function pure and easily testable.

Shared Kernel Types

Basic type aliases that do not introduce external dependencies:

type Email = string;
type UniqueId = string;
type DateTimeString = string;
type PriceCents = number;

Design Improvements

Rich price model : replace PriceCents with an object { value: number, currency: string } to capture currency information.

Feature‑based folder layout : organize code by feature (e.g., cart/, order/) instead of strict layer folders.

Shared utilities : extract helpers such as currentDatetime() to avoid hidden side‑effects.

Explicit dependency injection : pass required services to use‑cases via parameters or a DI container, keeping use‑cases pure.

Benefits of Clean Architecture

Domain independence : business logic is isolated and highly testable.

Replaceable third‑party services : adapters allow swapping implementations without touching core code.

High cohesion, low coupling : each layer has a single responsibility.

Costs and Trade‑offs

Time overhead : additional design and implementation effort.

Potential over‑engineering : small projects may become unnecessarily complex.

Bundle size increase : extra files add to the final JavaScript payload.

Conclusion

Applying Clean Architecture to a frontend project yields clear separation of concerns, easier testing, and flexibility to replace external services. While it introduces upfront design work and a larger bundle, the architectural benefits become evident as the codebase grows and requirements evolve.

Summary diagram
Summary diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendTypeScriptReactDomain-Driven DesignClean Architecturemodular designDependency Rule
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.