Share
1

Architectural Patterns in React: Components, Design, and Structure

by ObserverPoint · July 13, 2025

As React applications grow in size and complexity, simply throwing components into a single folder or mixing all concerns within one component quickly leads to a tangled and unmanageable codebase. Establishing clear architectural patterns is crucial for maintaining readability, reusability, testability, and scalability. This article will delve into three fundamental concepts that help structure large React applications: the Container/Presentational components pattern, Atomic Design principles, and effective folder structures.

Adopting these patterns, especially when combined with state management solutions like Redux Toolkit and type safety from TypeScript, provides a robust framework for building enterprise-grade applications that are easy to develop, debug, and evolve over time.


Container/Presentational Components Pattern

The Container/Presentational (or Smart/Dumb) components pattern, popularized by Dan Abramov, advocates for separating concerns within your React component tree. It divides components into two main categories:

1. Presentational (or “Dumb”) Components

  • Concern: How things look.
  • Characteristics:
    • Receive data and callbacks via props.
    • Rarely have their own state (unless it’s UI state like a toggle).
    • Focus on rendering UI elements based on props.
    • Are typically stateless functional components (or functional components using `useState` for local UI state).
    • Highly reusable and easily testable.
  • Example: A `Button`, `UserAvatar`, `ProductCard`, `List` component. They don’t know where the data comes from or how actions are handled; they just render what they’re told.
<!-- src/components/Button/Button.tsx -->
import React from 'react';
import './Button.css'; // Optional styling

interface ButtonProps {
    onClick: () => void;
    children: React.ReactNode;
    isDisabled?: boolean;
}

// Presentational component: focuses on rendering
const Button: React.FC<ButtonProps> = ({ onClick, children, isDisabled = false }) => {
    return (
        <button onClick={onClick} disabled={isDisabled} className="my-button">
            {children}
        </button>
    );
};

export default Button;
    

2. Container (or “Smart”) Components

  • Concern: How things work.
  • Characteristics:
    • Fetch data (e.g., from Redux store, API calls).
    • Contain business logic and state management.
    • Pass data and callbacks to presentational child components via props.
    • Are often connected to the Redux store (using `useSelector` and `useDispatch`).
    • Less reusable (specific to application logic), but easier to manage due to focused responsibility.
  • Example: A `UserListContainer` (fetches users, passes to `UserList`), `ProductPage` (fetches product data, handles adding to cart).
<!-- src/containers/CounterContainer.tsx -->
import React from 'react';
import { useAppSelector, useAppDispatch } from '../app/hooks'; // Your typed Redux hooks
import { increment, decrement } from '../features/counter/counterSlice'; // Redux actions
import Button from '../components/Button/Button'; // Presentational Button component

// Container component: manages state and logic, passes to presentational components
const CounterContainer: React.FC = () => {
    const count = useAppSelector((state) => state.counter.value);
    const dispatch = useAppDispatch();

    const handleIncrement = () => {
        dispatch(increment());
    };

    const handleDecrement = () => {
        dispatch(decrement());
    };

    return (
        <div>
            <h2>Counter Value: {count}</h2>
            <Button onClick={handleIncrement}>Increment</Button>
            <Button onClick={handleDecrement}>Decrement</Button>
        </div>
    );
};

export default CounterContainer;
    

Benefits: Clear separation of concerns, improved reusability of presentational components, easier testing (presentational components can be tested in isolation with mock props, container components can be tested for logic). While strict adherence can sometimes lead to an extra layer of components, it’s a valuable mental model for structuring your application.


Atomic Design Principles

Atomic Design, created by Brad Frost, is a methodology for crafting design systems. It breaks down UI into smaller, more manageable pieces and then combines them to create larger, more complex ones. This approach maps very well to React’s component-based architecture:

  1. Atoms: The smallest fundamental building blocks of matter. In UI, these are single HTML elements or basic React components that cannot be broken down further without losing their meaning. They serve as the foundational elements of our interfaces.
    • Examples: Labels, inputs, buttons, icons, headings, paragraphs.
    • React Mapping: Smallest, most reusable presentational components.
  2. Molecules: Groups of atoms bonded together to form a relatively simple, functional unit. They take on properties of their own, becoming distinct components.
    • Examples: A search form (input + button), a navigation item (icon + text link), an alert message (text + close button).
  3. Organisms: Relatively complex UI components composed of groups of molecules and/or atoms that form a distinct section of an interface. They are often self-contained and can be reused on different pages.
    • Examples: A header (logo, navigation, search bar), a product grid (multiple product cards), a user profile block.
  4. Templates: Page-level objects that place organisms into a layout. They focus on the underlying content structure rather than actual content, providing context to organisms and showing how they interact.
    • Examples: A blog post template, a product listing template. These are often wireframes or abstract page layouts.
    • React Mapping: Page-level components that arrange Organisms, often passing down data fetched by their Container parent.
  5. Pages: Specific instances of templates, with real content populated into them. Pages are the highest level of fidelity and are where you see the design system truly come to life.
    • Examples: The actual homepage, a specific blog post, a specific product detail page.
    • React Mapping: The actual routes in your application (e.g., `/products/:id`, `/dashboard`). These often act as containers, fetching all necessary data and passing it down to the templates/organisms.

Benefits of Atomic Design: Provides a clear hierarchy for components, promotes consistency, enhances reusability, and makes it easier to manage a design system and its implementation in code. It provides a shared vocabulary between designers and developers.


Folder Structure for Large Applications

Choosing an effective folder structure is crucial for large-scale React applications. There are two main paradigms: grouping by type and grouping by feature. For larger applications, grouping by feature is generally preferred, often combined with a dedicated folder for common utilities and shared UI components.

1. Grouping by Feature (Recommended for Large Apps)

In this structure, all files related to a specific feature (components, Redux slices, hooks, utilities, styles) are kept together in a single folder. This makes it easy to find all relevant code for a feature and to delete or refactor features independently.

src/
├── app/                ┌── Core application setup (Redux store, hooks, routing config)
│   ├── store.ts
│   ├── hooks.ts
│   └── AppRouter.tsx
├── assets/             ┌── Images, fonts, static files
├── components/         ┌── Reusable presentational components (Atoms, Molecules from Atomic Design)
│   ├── Button/
│   │   ├── Button.tsx
│   │   └── Button.module.css
│   ├── UserAvatar/
│   │   ├── UserAvatar.tsx
│   │   └── UserAvatar.module.css
│   └── common-ui-library/ ┌── Potentially a separate UI component library
├── features/           ┌── Main features, grouped by business domain
│   ├── auth/              ┌── Authentication feature
│   │   ├── AuthPage.tsx       ┌── Feature's main component/page
│   │   ├── authSlice.ts     ┌── Redux slice for auth state
│   │   ├── authApi.ts       ┌── API calls related to auth
│   │   ├── LoginForm.tsx
│   │   └── RegisterForm.tsx
│   ├── products/          ┌── Products feature
│   │   ├── ProductsPage.tsx
│   │   ├── ProductCard.tsx
│   │   ├── productsSlice.ts
│   │   ├── productApi.ts
│   │   └── ProductDetail.tsx
│   └── cart/              ┌── Shopping cart feature
├── layouts/            ┌── Major page layouts (e.g., Header, Sidebar, Footer)
│   ├── MainLayout.tsx
│   └── AuthLayout.tsx
├── pages/              ┌── Top-level components that map to routes (often containers/templates)
│   ├── HomePage.tsx
│   ├── DashboardPage.tsx
│   └── NotFoundPage.tsx
├── services/           ┌── Generic API services or external integrations (e.g., axios config)
│   └── api.ts
├── utils/              ┌── Helper functions, common utilities
│   ├── helpers.ts
│   └── constants.ts
└── App.tsx             ┌── Main application component (often contains routing)
    

Key considerations for folder structure:

  • Consistency: Whatever structure you choose, apply it consistently throughout the project.
  • Scalability: Can it easily accommodate new features without becoming messy?
  • Discoverability: Can developers quickly find the code they need?
  • Maintainability: Is it easy to refactor or remove parts of the application?
  • Flat is better than deep: Avoid excessively deep nesting of folders.

This feature-based structure, combined with a clear distinction between common UI components (`components`) and domain-specific UI/logic (`features`), provides a highly scalable and maintainable foundation for large React applications. It aligns well with both the Container/Presentational pattern and Atomic Design by allowing you to define smaller, reusable UI pieces in `components` and then compose them into larger, feature-specific sections within `features` and `pages`.


References

You may also like