Episode 2 — React Frontend Architecture NextJS / 2.1 — Introduction to React

2.1.h — Component-Based Architecture

In one sentence: Component-based architecture breaks complex UIs into small, reusable, self-contained pieces that can be developed, tested, and composed independently — it is the foundational mental model behind every React application.

Navigation: ← 2.1.g — JSX Syntax Rules · Next → Exercise Questions


Table of Contents

  1. What Is Component-Based Architecture?
  2. A Brief History — From Pages to Components
  3. The Component Tree
  4. Anatomy of a React Component
  5. Types of Components — Presentational vs Container
  6. Breaking Down a Real UI — Twitter/X Feed
  7. Breaking Down an E-Commerce Page
  8. When to Split a Component
  9. Component Naming Conventions
  10. File Organization Strategies
  11. Component Composition Patterns
  12. Component Communication Overview
  13. The Reusability Spectrum
  14. Common Anti-Patterns
  15. Real-World Component Library Design
  16. Key Takeaways

1. What Is Component-Based Architecture?

The Core Idea

Component-based architecture is a design philosophy where you build an application by assembling independent, self-contained units — each responsible for a specific piece of the user interface. Instead of thinking about a web page as one giant HTML document, you think about it as a tree of nested components, each managing its own rendering logic, data, and behavior.

Traditional approach:
  "I'm building a page."

Component approach:
  "I'm building a Navbar, a Sidebar, a ProductList,
   and each ProductCard inside it."

Why Components?

The problems components solve:

ProblemWithout ComponentsWith Components
Code duplicationCopy-paste HTML blocks for every product cardWrite <ProductCard /> once, reuse everywhere
MaintenanceChange one button style → search-and-replace across 40 filesChange <Button /> component → every button updates
Mental loadUnderstand 3000-line file to change one thingUnderstand 50-line component in isolation
Team collaborationTwo developers editing the same file → merge conflictsEach developer owns different components
TestingTest entire page behaviorTest each component independently
ConsistencyEach page renders cards slightly differentlySame <ProductCard /> guarantees visual consistency

The Lego Analogy

Think of components like Lego bricks:

┌─────────────────────────────────────────────────┐
│                   APPLICATION                    │
│                                                  │
│   ┌──────────┐  ┌──────────────────────────┐    │
│   │          │  │        MAIN CONTENT       │    │
│   │ SIDEBAR  │  │                           │    │
│   │          │  │  ┌──────┐ ┌──────┐       │    │
│   │ ┌──────┐ │  │  │ CARD │ │ CARD │       │    │
│   │ │ NAV  │ │  │  │      │ │      │       │    │
│   │ │ ITEM │ │  │  │┌────┐│ │┌────┐│       │    │
│   │ └──────┘ │  │  ││BTN ││ ││BTN ││       │    │
│   │ ┌──────┐ │  │  │└────┘│ │└────┘│       │    │
│   │ │ NAV  │ │  │  └──────┘ └──────┘       │    │
│   │ │ ITEM │ │  │                           │    │
│   │ └──────┘ │  │  ┌──────┐ ┌──────┐       │    │
│   │          │  │  │ CARD │ │ CARD │       │    │
│   └──────────┘  │  └──────┘ └──────┘       │    │
│                  └──────────────────────────┘    │
│                                                  │
│   ┌────────────────────────────────────────┐    │
│   │              FOOTER                     │    │
│   └────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

Each box is a component. Each component:

  • Has a clear boundary (what it renders)
  • Is self-contained (owns its logic)
  • Can be reused (Card appears 4 times)
  • Can be composed (Card contains Button)

Components in Other Paradigms

Component-based thinking isn't unique to React:

FrameworkComponent ConceptSyntax
ReactFunction returning JSXfunction Card() { return <div>...</div> }
VueSingle File Component (.vue)<template>, <script>, <style> in one file
AngularClass with decorator@Component({ template: '...' })
Svelte.svelte fileHTML + <script> + <style> in one file
Web ComponentsNative browser standardclass Card extends HTMLElement {}
SwiftUIStruct conforming to Viewstruct Card: View { var body: some View {...} }
FlutterWidget classclass Card extends StatelessWidget {}

React popularized the component mental model in web development, but the idea transcends any single framework.


2. A Brief History — From Pages to Components

The Page-Centric Era (2000–2010)

In early web development, the fundamental unit was a page. Each URL mapped to a complete HTML file. Server-side languages (PHP, JSP, ASP) generated full HTML pages on every request.

Traditional Server-Side Rendering:

Browser → Request /products → Server
                                 │
                                 ├── header.php (include)
                                 ├── sidebar.php (include)
                                 ├── product-list.php (main logic)
                                 └── footer.php (include)
                                 │
                                 ↓
                        Full HTML page ← Browser

"Components" in this era were server-side includes — chunks of HTML injected into templates. They had no interactivity, no state, and no encapsulated behavior.

The jQuery Era (2006–2013)

jQuery gave developers the ability to manipulate the DOM after page load. But code organization was behavior-centric, not component-centric:

// jQuery: organized by behavior, not by component
$(document).ready(function() {
  // Toggle sidebar
  $('.sidebar-toggle').click(function() {
    $('.sidebar').toggleClass('open');
  });

  // Product card hover effect
  $('.product-card').hover(
    function() { $(this).addClass('highlighted'); },
    function() { $(this).removeClass('highlighted'); }
  );

  // Add to cart
  $('.add-to-cart-btn').click(function() {
    var productId = $(this).data('product-id');
    $.post('/api/cart', { id: productId });
  });
});

Problem: The sidebar toggle, card hover, and cart logic all live in one file, even though they belong to completely different parts of the UI.

The MVC/MVVM Era (2010–2014)

Frameworks like Backbone.js, Ember.js, and AngularJS introduced Models, Views, and Controllers to organize client-side code:

MVC Organization:

models/
  Product.js        ← Data structure
  Cart.js
views/
  ProductListView.js  ← Rendering logic
  CartView.js
controllers/
  ProductController.js  ← Business logic
  CartController.js
templates/
  product-list.html    ← HTML templates
  cart.html

Better organized, but a single feature (like "Product Card") was split across 3-4 files in different directories. Changing one card meant touching model, view, controller, and template.

The Component Era (2013–Present)

React introduced the idea that everything about a component lives together:

// React: organized by component, not by concern
function ProductCard({ product, onAddToCart }) {
  const [isHovered, setIsHovered] = useState(false);

  return (
    <div
      className={`product-card ${isHovered ? 'highlighted' : ''}`}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => onAddToCart(product.id)}>
        Add to Cart
      </button>
    </div>
  );
}

One component, one file. Structure, behavior, and (optionally) styling are co-located. This is the architecture React champions.

Timeline Summary

2000        2006        2010        2013        2018        2024
  │           │           │           │           │           │
  ▼           ▼           ▼           ▼           ▼           ▼
Pages      jQuery      MVC/MVVM    Components  Hooks      Server
(PHP,      (behavior-  (Backbone,  (React,     (Function  Components
 JSP)      centric)    Angular 1)  Vue)        components) (RSC)
  │           │           │           │           │           │
  └───────────┴───────────┴───────────┴───────────┴───────────┘
         Increasing component-centric thinking →

3. The Component Tree

What Is a Component Tree?

Every React application forms a tree structure — a hierarchy of components where each component can contain zero or more child components. The topmost component is conventionally called App.

                        App
                       / | \
                      /  |  \
                 Navbar  Main  Footer
                 /  \      |       \
              Logo  Nav  Content  Copyright
                   / | \    |
            NavItem × 4  ProductGrid
                          / | | \
                   ProductCard × N
                      / | \
                  Image Title Price

Parent-Child Relationships

// App is the parent of Navbar, Main, and Footer
function App() {
  return (
    <div className="app">
      <Navbar />    {/* child 1 */}
      <Main />      {/* child 2 */}
      <Footer />    {/* child 3 */}
    </div>
  );
}

// Navbar is the parent of Logo and Nav
function Navbar() {
  return (
    <header>
      <Logo />      {/* child of Navbar */}
      <Nav />       {/* child of Navbar */}
    </header>
  );
}

// Nav is the parent of multiple NavItems
function Nav() {
  const links = ['Home', 'Products', 'About', 'Contact'];
  return (
    <nav>
      {links.map(label => (
        <NavItem key={label} label={label} />  {/* children of Nav */}
      ))}
    </nav>
  );
}

Key Tree Properties

PropertyDescriptionExample
RootThe topmost componentApp
LeafA component with no childrenLogo, NavItem, Copyright
DepthHow many levels deep a component isNavItem is depth 4 (App → Navbar → Nav → NavItem)
SiblingsComponents sharing the same parentNavbar, Main, Footer are siblings
SubtreeA component and all its descendantsThe Navbar subtree includes Logo, Nav, and NavItems

Data Flow in the Tree

React enforces unidirectional data flow — data flows down through props, and events flow up through callback functions:

┌─────────────────────────────────────────┐
│                  App                     │
│         state: { cartItems: [] }         │
│                                          │
│    props ↓ down          events ↑ up     │
│                                          │
│  ┌──────────────────────────────────┐   │
│  │          ProductGrid              │   │
│  │   props: { products, onAddToCart }│   │
│  │                                   │   │
│  │    ┌──────────┐ ┌──────────┐     │   │
│  │    │ProductCard│ │ProductCard│     │   │
│  │    │props:     │ │props:     │     │   │
│  │    │ product   │ │ product   │     │   │
│  │    │ onAdd     │ │ onAdd     │     │   │
│  │    └──────────┘ └──────────┘     │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Data:   App → ProductGrid → ProductCard  (down via props)
Events: ProductCard → ProductGrid → App  (up via callbacks)

Rendering and the Tree

When a component's state changes, React re-renders that component and all its descendants (by default). This is why tree structure matters for performance:

State change in App:
  → Re-renders App
    → Re-renders Navbar, Main, Footer
      → Re-renders Logo, Nav, Content, Copyright
        → Re-renders all NavItems, ProductGrid
          → Re-renders all ProductCards

State change in ProductGrid:
  → Re-renders ProductGrid
    → Re-renders all ProductCards
  (Navbar, Footer, etc. are NOT affected)

Lesson: Keep state as close to where it's needed as possible. If only ProductGrid needs some state, don't put it in App.


4. Anatomy of a React Component

The Minimal Component

// The simplest possible React component
function Greeting() {
  return <h1>Hello, World!</h1>;
}

That's it. A React component is a JavaScript function that returns JSX. But real components have more parts:

Full Anatomy

// ─── Imports ───────────────────────────────────────
import { useState, useEffect } from 'react';      // React hooks
import { formatCurrency } from '../utils/format';  // Utility functions
import Button from './Button';                      // Child components
import styles from './ProductCard.module.css';      // Styles

// ─── Type Definitions (TypeScript) ─────────────────
interface ProductCardProps {
  product: {
    id: string;
    name: string;
    price: number;
    image: string;
    inStock: boolean;
  };
  onAddToCart: (productId: string) => void;
  featured?: boolean;  // optional prop
}

// ─── Component Function ────────────────────────────
function ProductCard({ product, onAddToCart, featured = false }: ProductCardProps) {

  // ─── State ─────────────────────────────────────
  const [quantity, setQuantity] = useState(1);
  const [isExpanded, setIsExpanded] = useState(false);

  // ─── Derived Values (computed from state/props) ──
  const totalPrice = product.price * quantity;
  const formattedPrice = formatCurrency(totalPrice);
  const cardClass = `${styles.card} ${featured ? styles.featured : ''}`;

  // ─── Side Effects ──────────────────────────────
  useEffect(() => {
    console.log(`ProductCard mounted: ${product.name}`);
    return () => {
      console.log(`ProductCard unmounted: ${product.name}`);
    };
  }, [product.name]);

  // ─── Event Handlers ────────────────────────────
  function handleAddClick() {
    onAddToCart(product.id);
    setQuantity(1); // reset after adding
  }

  function handleQuantityChange(newQty) {
    if (newQty >= 1 && newQty <= 99) {
      setQuantity(newQty);
    }
  }

  // ─── Early Returns (guard clauses) ─────────────
  if (!product.inStock) {
    return (
      <div className={`${styles.card} ${styles.outOfStock}`}>
        <img src={product.image} alt={product.name} />
        <h3>{product.name}</h3>
        <p className={styles.soldOut}>Sold Out</p>
      </div>
    );
  }

  // ─── Main Render ───────────────────────────────
  return (
    <div className={cardClass}>
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p className={styles.price}>{formattedPrice}</p>

      <div className={styles.quantity}>
        <button onClick={() => handleQuantityChange(quantity - 1)}>−</button>
        <span>{quantity}</span>
        <button onClick={() => handleQuantityChange(quantity + 1)}>+</button>
      </div>

      <Button variant="primary" onClick={handleAddClick}>
        Add to Cart
      </Button>

      {isExpanded && (
        <div className={styles.details}>
          <p>Product ID: {product.id}</p>
        </div>
      )}
    </div>
  );
}

// ─── Export ─────────────────────────────────────────
export default ProductCard;

Anatomy Breakdown

┌─────────────────────────────────────────┐
│ 1. IMPORTS                               │
│    - React hooks (useState, useEffect)   │
│    - Utility functions                   │
│    - Child components                    │
│    - Styles (CSS modules, Tailwind, etc)│
├─────────────────────────────────────────┤
│ 2. TYPE DEFINITIONS (TypeScript)         │
│    - Props interface                     │
│    - Internal types                      │
├─────────────────────────────────────────┤
│ 3. COMPONENT FUNCTION                    │
│    │                                     │
│    ├── Props destructuring               │
│    ├── State declarations (useState)     │
│    ├── Derived/computed values           │
│    ├── Side effects (useEffect)          │
│    ├── Event handlers                    │
│    ├── Early returns (guard clauses)     │
│    └── Main return (JSX)                 │
├─────────────────────────────────────────┤
│ 4. EXPORT                                │
│    - default or named export             │
└─────────────────────────────────────────┘

What a Component Receives vs What It Owns

AspectReceived (External)Owned (Internal)
DataProps from parentState via useState
BehaviorCallback functions via propsEvent handlers defined inside
Identitykey prop for list renderingComponent function name
Childrenchildren propChild components in JSX return

5. Types of Components — Presentational vs Container

The Classic Distinction

Dan Abramov (Redux creator, React team member) popularized the distinction between presentational and container components in 2015. While he later softened his stance (hooks made the line blurry), the mental model remains useful.

Presentational Components (Dumb Components)

These components only care about how things look. They receive data via props and render UI. They have no idea where data comes from.

// Presentational: only renders, no data fetching or business logic
function UserAvatar({ name, imageUrl, size = 'medium' }) {
  const sizeMap = {
    small: 32,
    medium: 48,
    large: 64,
  };

  const pixels = sizeMap[size];

  return (
    <div className="avatar" style={{ width: pixels, height: pixels }}>
      <img
        src={imageUrl}
        alt={`${name}'s avatar`}
        width={pixels}
        height={pixels}
        className="avatar-image"
      />
    </div>
  );
}

// Presentational: renders a list of items, doesn't know where items come from
function ItemList({ items, renderItem }) {
  if (items.length === 0) {
    return <p className="empty-message">No items found.</p>;
  }

  return (
    <ul className="item-list">
      {items.map(item => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

Characteristics:

  • Receive all data through props
  • Rarely have state (maybe UI state like isOpen)
  • No side effects (no useEffect for data fetching)
  • Highly reusable across different contexts
  • Easy to test — pass props, check output

Container Components (Smart Components)

These components manage data and behavior. They fetch data, handle business logic, and pass results to presentational components.

// Container: fetches data, manages state, delegates rendering
function UserProfileContainer({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch user');
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;
  if (!user) return null;

  // Delegates rendering to presentational components
  return (
    <div className="user-profile">
      <UserAvatar name={user.name} imageUrl={user.avatar} size="large" />
      <UserInfo name={user.name} email={user.email} bio={user.bio} />
      <UserStats posts={user.postCount} followers={user.followerCount} />
    </div>
  );
}

Characteristics:

  • Fetch data (API calls, database queries)
  • Manage complex state
  • Contain business logic
  • Pass data down to presentational components
  • Less reusable — tied to specific data sources

Comparison Table

AspectPresentationalContainer
PurposeHow things lookHow things work
DataFrom props onlyFetches/manages its own
StateMinimal (UI-only)Complex (business data)
Side effectsNoneData fetching, subscriptions
ReusabilityHighLow
TestingEasy (pure render)Harder (mock APIs, state)
Also calledDumb, Stateless, UISmart, Stateful, Logic

Modern Take — Hooks Changed Things

With hooks, the container/presentational split often becomes a component + custom hook split:

// Custom hook extracts the "container" logic
function useUser(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);

  return { user, loading, error };
}

// Component uses the hook — no separate container needed
function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;

  return (
    <div className="user-profile">
      <UserAvatar name={user.name} imageUrl={user.avatar} size="large" />
      <UserInfo name={user.name} email={user.email} bio={user.bio} />
    </div>
  );
}

This is the modern pattern: custom hooks replace container components as the primary way to extract and reuse business logic.


6. Breaking Down a Real UI — Twitter/X Feed

The Exercise

Let's take a real-world UI and decompose it into components. This is the single most important skill in React development.

Twitter/X Feed Layout

┌─────────────────────────────────────────────────────────────┐
│ ┌─────────┐  ┌───────────────────────────┐  ┌───────────┐ │
│ │         │  │        HEADER BAR          │  │           │ │
│ │         │  │  Logo  Search   Profile    │  │           │ │
│ │         │  └───────────────────────────┘  │           │ │
│ │         │                                  │           │ │
│ │ SIDEBAR │  ┌───────────────────────────┐  │  TRENDING │ │
│ │         │  │     COMPOSE TWEET BOX      │  │  SIDEBAR  │ │
│ │ Home    │  │  ┌─────────────────────┐  │  │           │ │
│ │ Search  │  │  │  Avatar  Textarea   │  │  │ Trend 1   │ │
│ │ Notifs  │  │  └─────────────────────┘  │  │ Trend 2   │ │
│ │ Messages│  │  Media  GIF  Poll  Post   │  │ Trend 3   │ │
│ │ Profile │  └───────────────────────────┘  │           │ │
│ │         │                                  │ ┌───────┐ │ │
│ │         │  ┌───────────────────────────┐  │ │Follow  │ │ │
│ │         │  │         TWEET 1            │  │ │Suggest │ │ │
│ │         │  │  ┌────┐                   │  │ │       │ │ │
│ │         │  │  │Ava │ @username · 2h    │  │ │User 1 │ │ │
│ │         │  │  └────┘                   │  │ │User 2 │ │ │
│ │         │  │  Tweet content text...    │  │ │User 3 │ │ │
│ │         │  │  ┌──────────────────┐     │  │ └───────┘ │ │
│ │         │  │  │   Image/Media    │     │  │           │ │
│ │         │  │  └──────────────────┘     │  │           │ │
│ │         │  │  💬 12  🔄 45  ❤ 230  📤 │  │           │ │
│ │         │  └───────────────────────────┘  │           │ │
│ │         │                                  │           │ │
│ │         │  ┌───────────────────────────┐  │           │ │
│ │         │  │         TWEET 2            │  │           │ │
│ │         │  │  (same structure as above) │  │           │ │
│ │         │  └───────────────────────────┘  │           │ │
│ └─────────┘  └───────────────────────────┘  └───────────┘ │
└─────────────────────────────────────────────────────────────┘

Component Decomposition

App
├── Layout
│   ├── Sidebar
│   │   ├── Logo
│   │   ├── NavItem (× 6: Home, Search, Notifs, Messages, Bookmarks, Profile)
│   │   └── TweetButton
│   ├── MainFeed
│   │   ├── FeedHeader
│   │   │   └── TabGroup
│   │   │       └── Tab (× 2: "For You", "Following")
│   │   ├── ComposeTweet
│   │   │   ├── Avatar
│   │   │   ├── TweetTextarea
│   │   │   └── TweetActions
│   │   │       ├── MediaButton
│   │   │       ├── GifButton
│   │   │       ├── PollButton
│   │   │       └── PostButton
│   │   └── TweetList
│   │       └── Tweet (× N)
│   │           ├── Avatar
│   │           ├── TweetHeader (username, handle, timestamp)
│   │           ├── TweetContent (text)
│   │           ├── TweetMedia (optional: image, video, link preview)
│   │           └── TweetActions (reply, retweet, like, share)
│   └── TrendingSidebar
│       ├── SearchBar
│       ├── TrendingSection
│       │   └── TrendItem (× N)
│       └── FollowSuggestions
│           └── SuggestedUser (× N)
│               ├── Avatar
│               ├── UserInfo
│               └── FollowButton

Translating to Code

// Level 0: App
function App() {
  return (
    <Layout>
      <Sidebar />
      <MainFeed />
      <TrendingSidebar />
    </Layout>
  );
}

// Level 1: MainFeed
function MainFeed() {
  const { tweets, loading } = useTweets();

  return (
    <main className="main-feed">
      <FeedHeader />
      <ComposeTweet />
      <TweetList tweets={tweets} loading={loading} />
    </main>
  );
}

// Level 2: TweetList
function TweetList({ tweets, loading }) {
  if (loading) return <FeedSkeleton />;

  return (
    <div className="tweet-list">
      {tweets.map(tweet => (
        <Tweet key={tweet.id} tweet={tweet} />
      ))}
    </div>
  );
}

// Level 3: Tweet (a reusable component)
function Tweet({ tweet }) {
  return (
    <article className="tweet">
      <Avatar src={tweet.author.avatar} alt={tweet.author.name} />
      <div className="tweet-body">
        <TweetHeader author={tweet.author} timestamp={tweet.createdAt} />
        <TweetContent text={tweet.text} />
        {tweet.media && <TweetMedia media={tweet.media} />}
        <TweetActions
          replyCount={tweet.replies}
          retweetCount={tweet.retweets}
          likeCount={tweet.likes}
          tweetId={tweet.id}
        />
      </div>
    </article>
  );
}

Why This Decomposition?

ComponentReason for Separation
AvatarReused in tweets, compose box, suggestions
TweetActionsComplex interaction logic (like, retweet, share)
TweetMediaDifferent media types need different renderers
TweetHeaderConsistent author display across tweet types
NavItemRepeated sidebar items with icon + label
TrendItemRepeated trend entries with topic + count

7. Breaking Down an E-Commerce Page

Product Listing Page Layout

┌──────────────────────────────────────────────────────────┐
│                     HEADER                                │
│  ┌──────┐  ┌─────────────────┐  ┌────┐ ┌────┐ ┌──────┐ │
│  │ Logo │  │   Search Bar    │  │Cart│ │User│ │ Menu │ │
│  └──────┘  └─────────────────┘  └────┘ └────┘ └──────┘ │
├──────────────────────────────────────────────────────────┤
│  ┌──────────┐                                            │
│  │ BREADCRUMB│  Electronics > Laptops                    │
│  └──────────┘                                            │
│                                                           │
│  ┌──────────┐  ┌─────────────────────────────────────┐  │
│  │ FILTERS  │  │  PRODUCT GRID                        │  │
│  │          │  │                                      │  │
│  │ Price    │  │  Sort: [Relevance ▼]  Showing 1-24  │  │
│  │ ┌──────┐ │  │                                      │  │
│  │ │$0-$50│ │  │  ┌──────────┐  ┌──────────┐        │  │
│  │ │$50+  │ │  │  │ Product  │  │ Product  │        │  │
│  │ └──────┘ │  │  │ ┌──────┐ │  │ ┌──────┐ │        │  │
│  │          │  │  │ │Image │ │  │ │Image │ │        │  │
│  │ Brand   │  │  │ └──────┘ │  │ └──────┘ │        │  │
│  │ ┌──────┐ │  │  │ Title   │  │ Title   │        │  │
│  │ │Apple │ │  │  │ ★★★★☆  │  │ ★★★★★  │        │  │
│  │ │Dell  │ │  │  │ $999    │  │ $1,299  │        │  │
│  │ │HP    │ │  │  │ [Add]   │  │ [Add]   │        │  │
│  │ └──────┘ │  │  └──────────┘  └──────────┘        │  │
│  │          │  │                                      │  │
│  │ Rating  │  │  ┌──────────┐  ┌──────────┐        │  │
│  │ ★★★★+  │  │  │ Product  │  │ Product  │        │  │
│  │ ★★★+   │  │  │  ...     │  │  ...     │        │  │
│  │          │  │  └──────────┘  └──────────┘        │  │
│  └──────────┘  │                                      │  │
│                 │  ┌──────────────────────────┐       │  │
│                 │  │   PAGINATION              │       │  │
│                 │  │  < 1  2  3  ...  12  >    │       │  │
│                 │  └──────────────────────────┘       │  │
│                 └─────────────────────────────────────┘  │
├──────────────────────────────────────────────────────────┤
│                     FOOTER                                │
└──────────────────────────────────────────────────────────┘

Component Tree

App
├── Header
│   ├── Logo
│   ├── SearchBar
│   ├── CartIcon (with badge count)
│   ├── UserMenu
│   └── HamburgerMenu (mobile)
├── Breadcrumb
│   └── BreadcrumbItem (× N)
├── ProductListingPage
│   ├── FilterSidebar
│   │   ├── FilterSection (× N: Price, Brand, Rating)
│   │   │   ├── FilterHeader (collapsible)
│   │   │   └── FilterOption (× N: checkboxes, ranges)
│   │   └── ClearFiltersButton
│   ├── ProductGrid
│   │   ├── SortBar
│   │   │   ├── SortDropdown
│   │   │   └── ResultCount
│   │   ├── ProductCard (× N)
│   │   │   ├── ProductImage
│   │   │   ├── ProductTitle
│   │   │   ├── StarRating
│   │   │   ├── Price (sale price, original price, discount badge)
│   │   │   └── AddToCartButton
│   │   └── Pagination
│   │       └── PageButton (× N)
│   └── MobileFilterToggle (responsive)
└── Footer
    ├── FooterLinkSection (× N)
    ├── NewsletterSignup
    └── Copyright

Reusable Components Identified

// StarRating — reused on product cards, product detail, reviews
function StarRating({ rating, maxStars = 5, showCount = false, reviewCount }) {
  return (
    <div className="star-rating" aria-label={`${rating} out of ${maxStars} stars`}>
      {Array.from({ length: maxStars }, (_, i) => (
        <Star key={i} filled={i < Math.floor(rating)} half={i === Math.floor(rating) && rating % 1 >= 0.5} />
      ))}
      {showCount && <span className="review-count">({reviewCount})</span>}
    </div>
  );
}

// Price — handles sale prices, currency formatting, discount badges
function Price({ original, sale, currency = 'USD' }) {
  const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency });

  if (!sale || sale >= original) {
    return <span className="price">{formatter.format(original)}</span>;
  }

  const discount = Math.round((1 - sale / original) * 100);

  return (
    <div className="price-group">
      <span className="price-sale">{formatter.format(sale)}</span>
      <span className="price-original">{formatter.format(original)}</span>
      <span className="price-discount">-{discount}%</span>
    </div>
  );
}

// Badge — generic badge used for "Sale", "New", "Bestseller"
function Badge({ variant = 'default', children }) {
  return <span className={`badge badge-${variant}`}>{children}</span>;
}

8. When to Split a Component

The Rules of Thumb

Knowing when to split a component is arguably more important than knowing how. Split too early → unnecessary complexity. Split too late → unmaintainable monoliths.

Rule 1: Single Responsibility Principle

A component should do one thing. If you can't describe what a component does in one sentence without using "and", it probably needs splitting.

// BAD: Does too many things
function Dashboard() {
  // Fetches user data AND notifications AND analytics
  // Renders sidebar AND main content AND charts AND tables
  // Handles navigation AND filtering AND sorting
  // 500 lines of JSX...
}

// GOOD: Each component has one job
function Dashboard() {
  return (
    <DashboardLayout>
      <DashboardSidebar />       {/* navigation */}
      <DashboardContent>
        <AnalyticsOverview />    {/* charts */}
        <RecentActivity />       {/* activity table */}
        <NotificationPanel />    {/* notifications */}
      </DashboardContent>
    </DashboardLayout>
  );
}

Rule 2: The 100-Line Guideline

Not a hard rule, but a useful heuristic:

Component SizeAssessmentAction
< 50 linesProbably fineLeave as-is
50–100 linesWatch for growthConsider splitting if adding more features
100–200 linesGetting complexLook for extraction opportunities
200+ linesToo largeAlmost certainly needs splitting

Rule 3: Reuse Detection

If you copy-paste JSX, you need a component:

// BEFORE: Copy-paste pattern detected
function ProfilePage() {
  return (
    <div>
      {/* Copy-paste 1 */}
      <div className="stat-card">
        <h3>Posts</h3>
        <p className="stat-number">142</p>
        <span className="stat-trend up">+12%</span>
      </div>

      {/* Copy-paste 2 */}
      <div className="stat-card">
        <h3>Followers</h3>
        <p className="stat-number">8,294</p>
        <span className="stat-trend up">+3%</span>
      </div>

      {/* Copy-paste 3 */}
      <div className="stat-card">
        <h3>Following</h3>
        <p className="stat-number">451</p>
        <span className="stat-trend down">-1%</span>
      </div>
    </div>
  );
}

// AFTER: Extract the pattern
function StatCard({ label, value, trend, direction }) {
  return (
    <div className="stat-card">
      <h3>{label}</h3>
      <p className="stat-number">{value}</p>
      <span className={`stat-trend ${direction}`}>{trend}</span>
    </div>
  );
}

function ProfilePage() {
  return (
    <div>
      <StatCard label="Posts" value="142" trend="+12%" direction="up" />
      <StatCard label="Followers" value="8,294" trend="+3%" direction="up" />
      <StatCard label="Following" value="451" trend="-1%" direction="down" />
    </div>
  );
}

Rule 4: Isolating Complexity

If one part of a component has complex logic (heavy computation, multiple states, side effects), extract it:

// BEFORE: Complex search logic mixed with layout
function ProductPage() {
  const [query, setQuery] = useState('');
  const [filters, setFilters] = useState({});
  const [sortBy, setSortBy] = useState('relevance');
  const [debouncedQuery, setDebouncedQuery] = useState('');
  // ... 30 more lines of search/filter logic

  return (
    <div>
      {/* ... lots of JSX for the whole page */}
    </div>
  );
}

// AFTER: Search complexity isolated
function SearchableProductGrid() {
  const { query, setQuery, filters, setFilters, sortBy, setSortBy, results } = useProductSearch();

  return (
    <div>
      <SearchBar value={query} onChange={setQuery} />
      <FilterBar filters={filters} onChange={setFilters} />
      <SortDropdown value={sortBy} onChange={setSortBy} />
      <ProductGrid products={results} />
    </div>
  );
}

Rule 5: The "Would I Test This Separately?" Test

If a piece of UI has behavior worth testing independently, it should be its own component:

"Would I want a test for just this part?"

  Password strength meter  → Yes → Extract <PasswordStrength />
  A plain <p> tag          → No  → Leave inline
  Form validation feedback → Yes → Extract <ValidationMessage />
  A wrapper <div>          → No  → Leave inline

When NOT to Split

SituationWhy Not to Split
Only used once, simple, < 30 linesOverhead of a new file isn't worth it
Tightly coupled logicSplitting would require passing 10+ props
Premature abstractionYou only have 1 use case — wait for the second
Performance won't improveSplitting doesn't help if parent re-renders anyway

9. Component Naming Conventions

PascalCase — The Non-Negotiable Rule

React components must start with an uppercase letter. This is how React distinguishes components from HTML elements:

// React sees lowercase → HTML element
<div>         // renders <div>
<header>      // renders <header>
<section>     // renders <section>

// React sees PascalCase → component
<ProductCard />    // renders ProductCard component
<UserProfile />    // renders UserProfile component
<App />            // renders App component
// WRONG: lowercase component name — React treats it as HTML
function productCard() { ... }
<productCard />  // React renders <productcard> HTML tag, not your component!

// CORRECT: PascalCase
function ProductCard() { ... }
<ProductCard />  // React renders your component

Naming Guidelines

PatternExampleWhen to Use
NounProductCard, UserAvatarMost components — they represent a thing
Adjective + NounFeaturedProduct, CollapsibleSectionVariants of a base component
Verb + Noun (rarely)SearchResultsComponents showing results of actions
With + Noun (HOC)withAuth, withThemeHigher-order components (legacy pattern)
use + Noun (hooks)useAuth, useFetchCustom hooks — always starts with "use"

Good vs Bad Names

BadBetterWhy
DataUserData, ProductListToo vague — what data?
WrapperCardLayout, PageContainer"Wrapper" says nothing about purpose
Component1HeaderNavigationNever use numbers
StuffDashboardWidgetsBe descriptive
MyButtonButton"My" adds no information
HandleClickSubmitButtonName the thing, not the handler
ItemTodoItem, CartItem"Item" needs context

File Naming Patterns

Different teams use different conventions. Pick one and stick with it:

Option A: Match component name (PascalCase)
  ProductCard.jsx
  ProductCard.module.css
  ProductCard.test.jsx

Option B: kebab-case files
  product-card.jsx
  product-card.module.css
  product-card.test.jsx

Option C: index files in folders
  ProductCard/
    index.jsx
    styles.module.css
    ProductCard.test.jsx

The critical rule: whatever convention you choose, be 100% consistent across the entire project.


10. File Organization Strategies

Strategy 1: Type-Based (Group by File Type)

src/
├── components/
│   ├── Button.jsx
│   ├── Card.jsx
│   ├── Modal.jsx
│   ├── Navbar.jsx
│   ├── ProductCard.jsx
│   └── UserAvatar.jsx
├── hooks/
│   ├── useAuth.js
│   ├── useFetch.js
│   └── useLocalStorage.js
├── pages/
│   ├── HomePage.jsx
│   ├── ProductPage.jsx
│   └── ProfilePage.jsx
├── services/
│   ├── api.js
│   ├── auth.js
│   └── products.js
├── utils/
│   ├── format.js
│   ├── validate.js
│   └── helpers.js
└── styles/
    ├── global.css
    ├── variables.css
    └── components/
        ├── Button.module.css
        └── Card.module.css

Pros: Simple, familiar, easy for beginners. Cons: Related files scattered across directories. As project grows, components/ becomes a dumping ground with 50+ files.

Strategy 2: Feature-Based (Group by Domain)

src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   │   ├── LoginForm.jsx
│   │   │   ├── SignupForm.jsx
│   │   │   └── AuthGuard.jsx
│   │   ├── hooks/
│   │   │   └── useAuth.js
│   │   ├── services/
│   │   │   └── authApi.js
│   │   └── index.js           ← public API of this feature
│   ├── products/
│   │   ├── components/
│   │   │   ├── ProductCard.jsx
│   │   │   ├── ProductGrid.jsx
│   │   │   └── ProductDetail.jsx
│   │   ├── hooks/
│   │   │   └── useProducts.js
│   │   ├── services/
│   │   │   └── productApi.js
│   │   └── index.js
│   └── cart/
│       ├── components/
│       │   ├── CartDrawer.jsx
│       │   ├── CartItem.jsx
│       │   └── CartSummary.jsx
│       ├── hooks/
│       │   └── useCart.js
│       └── index.js
├── shared/                     ← shared across features
│   ├── components/
│   │   ├── Button.jsx
│   │   ├── Modal.jsx
│   │   └── LoadingSpinner.jsx
│   ├── hooks/
│   │   ├── useFetch.js
│   │   └── useLocalStorage.js
│   └── utils/
│       ├── format.js
│       └── validate.js
├── pages/                      ← route-level components
│   ├── HomePage.jsx
│   ├── ProductPage.jsx
│   └── CartPage.jsx
└── App.jsx

Pros: All related code is co-located. Easy to understand and modify a feature. Scales well. Cons: Deciding which feature a component belongs to can be ambiguous. Shared components still need a separate directory.

Strategy 3: Atomic Design

Brad Frost's methodology organizes components by their complexity level:

src/
├── components/
│   ├── atoms/           ← smallest building blocks
│   │   ├── Button.jsx
│   │   ├── Input.jsx
│   │   ├── Label.jsx
│   │   ├── Icon.jsx
│   │   └── Badge.jsx
│   ├── molecules/       ← groups of atoms
│   │   ├── SearchBar.jsx        (Input + Button + Icon)
│   │   ├── FormField.jsx        (Label + Input + ErrorMessage)
│   │   ├── NavItem.jsx          (Icon + Label)
│   │   └── StarRating.jsx       (Icon × 5)
│   ├── organisms/       ← complex sections
│   │   ├── Navbar.jsx           (Logo + NavItem[] + SearchBar)
│   │   ├── ProductCard.jsx      (Image + Text + Price + Button)
│   │   ├── LoginForm.jsx        (FormField[] + Button)
│   │   └── Footer.jsx           (Links + Newsletter + Copyright)
│   ├── templates/       ← page layouts (structure, no data)
│   │   ├── DashboardLayout.jsx
│   │   ├── ProductListLayout.jsx
│   │   └── AuthLayout.jsx
│   └── pages/           ← templates with real data
│       ├── HomePage.jsx
│       ├── ProductPage.jsx
│       └── DashboardPage.jsx
Atoms       →    Molecules      →    Organisms       →    Templates    →    Pages
┌──────┐        ┌───────────┐        ┌──────────────┐     ┌──────────┐     ┌──────────┐
│Button│   →    │ SearchBar │   →    │   Navbar     │ →   │ Dashboard│ →   │ Dashboard│
│Input │        │(Input+Btn)│        │(Logo+Nav+    │     │ Layout   │     │ Page     │
│Icon  │        └───────────┘        │ SearchBar)   │     │(Navbar+  │     │(Layout + │
└──────┘                              └──────────────┘     │ Sidebar+ │     │ real data│
                                                           │ Content) │     │ from API)│
                                                           └──────────┘     └──────────┘

Comparison

StrategyBest ForTeam SizeProject Size
Type-basedSimple apps, learning1–3 devsSmall
Feature-basedBusiness apps, complex domains3–10 devsMedium–Large
Atomic DesignDesign systems, component librariesDesign + Dev teamsAny

Real-world recommendation: Most production React apps end up with a hybrid of feature-based + a shared component library.


11. Component Composition Patterns

The children Prop

The most fundamental composition pattern. Any JSX placed between a component's opening and closing tags is passed as the children prop:

// Card component accepts any children
function Card({ children, title }) {
  return (
    <div className="card">
      {title && <h2 className="card-title">{title}</h2>}
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Usage — compose different content into the same Card shell
<Card title="User Profile">
  <Avatar src="/avatar.jpg" />
  <p>John Doe</p>
  <p>Software Engineer</p>
</Card>

<Card title="Recent Orders">
  <OrderList orders={orders} />
</Card>

<Card title="Statistics">
  <BarChart data={stats} />
</Card>

The Slot Pattern (Named Children)

When a component needs multiple "slots" for different content areas, use props instead of children:

// Layout with named slots
function PageLayout({ header, sidebar, main, footer }) {
  return (
    <div className="page-layout">
      <header className="page-header">{header}</header>
      <div className="page-body">
        <aside className="page-sidebar">{sidebar}</aside>
        <main className="page-main">{main}</main>
      </div>
      <footer className="page-footer">{footer}</footer>
    </div>
  );
}

// Usage — compose the page from independent pieces
<PageLayout
  header={<Navbar user={currentUser} />}
  sidebar={<FilterPanel filters={filters} onChange={setFilters} />}
  main={<ProductGrid products={products} />}
  footer={<FooterLinks />}
/>

Compound Components Pattern

Components that work together, sharing implicit state:

// Tabs compound component — Tab and TabPanel work together
function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list" role="tablist">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      className={`tab ${activeTab === value ? 'active' : ''}`}
      onClick={() => setActiveTab(value)}
      role="tab"
      aria-selected={activeTab === value}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;
  return <div className="tab-panel" role="tabpanel">{children}</div>;
}

// Usage — components compose together naturally
<Tabs defaultTab="overview">
  <TabList>
    <Tab value="overview">Overview</Tab>
    <Tab value="reviews">Reviews</Tab>
    <Tab value="specs">Specifications</Tab>
  </TabList>

  <TabPanel value="overview">
    <ProductOverview product={product} />
  </TabPanel>
  <TabPanel value="reviews">
    <ReviewList productId={product.id} />
  </TabPanel>
  <TabPanel value="specs">
    <SpecificationTable specs={product.specs} />
  </TabPanel>
</Tabs>

Composition vs Inheritance

React strongly favors composition over inheritance:

// WRONG: Inheritance approach (don't do this in React)
class SpecialButton extends Button {
  render() {
    return <button className="special">{this.props.children}</button>;
  }
}

// CORRECT: Composition approach
function SpecialButton({ children, ...props }) {
  return (
    <Button className="special" {...props}>
      <SparkleIcon />
      {children}
    </Button>
  );
}

// Or even simpler — use props for variants
function Button({ variant = 'default', children, ...props }) {
  return (
    <button className={`btn btn-${variant}`} {...props}>
      {children}
    </button>
  );
}

// Usage
<Button variant="special">Click me</Button>
<Button variant="danger">Delete</Button>
<Button variant="ghost">Cancel</Button>

12. Component Communication Overview

Pattern 1: Props Down (Parent → Child)

The most basic communication. Parent passes data to child:

function Parent() {
  const user = { name: 'Alice', role: 'admin' };

  return (
    <div>
      <ChildA userName={user.name} />
      <ChildB userRole={user.role} />
    </div>
  );
}

function ChildA({ userName }) {
  return <h1>Welcome, {userName}!</h1>;
}

function ChildB({ userRole }) {
  return <span className="badge">{userRole}</span>;
}

Pattern 2: Callbacks Up (Child → Parent)

Child communicates events back to parent via callback functions:

function Parent() {
  const [items, setItems] = useState([]);

  function handleAddItem(newItem) {
    setItems(prev => [...prev, newItem]);
  }

  function handleRemoveItem(itemId) {
    setItems(prev => prev.filter(item => item.id !== itemId));
  }

  return (
    <div>
      <AddItemForm onAdd={handleAddItem} />
      <ItemList items={items} onRemove={handleRemoveItem} />
    </div>
  );
}

function AddItemForm({ onAdd }) {
  const [text, setText] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    onAdd({ id: Date.now(), text });
    setText('');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  );
}

Pattern 3: Lifting State Up (Sibling → Sibling)

Siblings can't communicate directly. They communicate through a shared parent:

┌────────────────────────────────────┐
│           Parent                    │
│   state: { temperature: 100 }      │
│                                     │
│   ┌──────────────┐ ┌──────────────┐│
│   │ Celsius      │ │ Fahrenheit   ││
│   │ Input        │ │ Display      ││
│   │              │ │              ││
│   │ onChange ──────────→ reads     ││
│   │ (updates     │ │ parent's     ││
│   │  parent)     │ │ state        ││
│   └──────────────┘ └──────────────┘│
└────────────────────────────────────┘
function TemperatureConverter() {
  const [celsius, setCelsius] = useState(0);
  const fahrenheit = (celsius * 9/5) + 32;

  return (
    <div>
      <CelsiusInput value={celsius} onChange={setCelsius} />
      <FahrenheitDisplay value={fahrenheit} />
    </div>
  );
}

function CelsiusInput({ value, onChange }) {
  return (
    <label>
      Celsius:
      <input
        type="number"
        value={value}
        onChange={e => onChange(Number(e.target.value))}
      />
    </label>
  );
}

function FahrenheitDisplay({ value }) {
  return <p>Fahrenheit: {value.toFixed(1)}°F</p>;
}

Communication Pattern Summary

DirectionPatternMechanism
Parent → ChildPropsPass data as attributes
Child → ParentCallbacksPass function via props, child invokes it
Sibling → SiblingLift state upShared parent holds state, passes to both
Distant componentsContext APIProvider at top, consumers anywhere below
GlobalState managementRedux, Zustand, etc.

13. The Reusability Spectrum

Components exist on a spectrum from hardcoded to fully generic. Neither extreme is always best.

Level 1: Hardcoded (Least Reusable)

// Only works for this exact use case
function WelcomeBanner() {
  return (
    <div style={{ background: 'blue', color: 'white', padding: 20 }}>
      <h1>Welcome to ShopMart!</h1>
      <p>Your one-stop shop for electronics.</p>
    </div>
  );
}

Level 2: Configurable via Props

// Works for any welcome banner
function WelcomeBanner({ title, subtitle, bgColor = 'blue' }) {
  return (
    <div style={{ background: bgColor, color: 'white', padding: 20 }}>
      <h1>{title}</h1>
      <p>{subtitle}</p>
    </div>
  );
}

Level 3: Flexible with Children

// Works for any banner content
function Banner({ bgColor = 'blue', children }) {
  return (
    <div style={{ background: bgColor, color: 'white', padding: 20 }}>
      {children}
    </div>
  );
}

// Usage
<Banner bgColor="green">
  <h1>Welcome!</h1>
  <p>Any content here.</p>
  <Button>Get Started</Button>
</Banner>

Level 4: Render Props / Compound Components

// Works for any layout pattern with this structure
function Banner({ bgColor, textColor, render }) {
  const theme = { bgColor, textColor };
  return (
    <div style={{ background: bgColor, color: textColor, padding: 20 }}>
      {render(theme)}
    </div>
  );
}

// Usage — consumer controls rendering
<Banner
  bgColor="purple"
  textColor="white"
  render={(theme) => (
    <div>
      <h1 style={{ color: theme.textColor }}>Custom Layout</h1>
      <AnimatedIcon />
      <CTAButton />
    </div>
  )}
/>

Level 5: Headless (Most Reusable)

No rendering — only provides behavior. The consumer provides all the UI:

// Headless toggle hook — provides logic, no UI
function useToggle(initialValue = false) {
  const [isOpen, setIsOpen] = useState(initialValue);
  const toggle = () => setIsOpen(prev => !prev);
  const open = () => setIsOpen(true);
  const close = () => setIsOpen(false);
  return { isOpen, toggle, open, close };
}

// Consumer provides all rendering
function Accordion({ title, children }) {
  const { isOpen, toggle } = useToggle(false);

  return (
    <div className="accordion">
      <button onClick={toggle}>
        {title} {isOpen ? '▼' : '▶'}
      </button>
      {isOpen && <div className="accordion-content">{children}</div>}
    </div>
  );
}

function Dropdown({ trigger, children }) {
  const { isOpen, toggle, close } = useToggle(false);

  return (
    <div className="dropdown" onBlur={close}>
      <button onClick={toggle}>{trigger}</button>
      {isOpen && <div className="dropdown-menu">{children}</div>}
    </div>
  );
}

Spectrum Summary

Hardcoded ←────────────────────────────────────→ Headless
   │                                                  │
   │  Simple, fast to build                          │  Maximum flexibility
   │  Zero configuration                             │  Consumer controls UI
   │  Not reusable                                   │  Highly reusable
   │  Tight coupling                                 │  Zero coupling
   │                                                  │
   └──── Use for: one-off layouts,           Use for: design systems,
         prototypes, unique pages            libraries, shared logic ────┘

The golden rule: Start at Level 2 (configurable via props). Move toward Level 3-4 only when you have concrete reuse cases, not hypothetical ones.


14. Common Anti-Patterns

Anti-Pattern 1: The God Component

A single component that does everything:

// BAD: God component — 800 lines, does EVERYTHING
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [notifications, setNotifications] = useState([]);
  const [settings, setSettings] = useState({});
  const [analytics, setAnalytics] = useState({});
  const [friends, setFriends] = useState([]);
  const [messages, setMessages] = useState([]);
  // ... 20 more state variables
  // ... 15 useEffect hooks
  // ... 30 event handlers
  // ... 400 lines of JSX

  return (
    <div>
      {/* Sidebar with navigation */}
      {/* Profile header with avatar, stats */}
      {/* Post feed with infinite scroll */}
      {/* Notification panel */}
      {/* Analytics charts */}
      {/* Settings form */}
      {/* Chat widget */}
      {/* Everything in one component... */}
    </div>
  );
}

Fix: Break into focused components, each managing its own data:

function UserDashboard() {
  return (
    <DashboardLayout>
      <DashboardSidebar />
      <DashboardMain>
        <ProfileHeader />
        <PostFeed />
      </DashboardMain>
      <DashboardAside>
        <NotificationPanel />
        <AnalyticsWidget />
      </DashboardAside>
    </DashboardLayout>
  );
}

Anti-Pattern 2: Prop Explosion

Passing too many props makes components hard to use and maintain:

// BAD: 15+ props — the component is trying to do too much
<UserCard
  name={user.name}
  email={user.email}
  avatar={user.avatar}
  role={user.role}
  joinDate={user.joinDate}
  postCount={user.posts.length}
  followerCount={user.followers.length}
  isOnline={user.online}
  isVerified={user.verified}
  showBadge={true}
  badgeText="PRO"
  onFollow={handleFollow}
  onMessage={handleMessage}
  onBlock={handleBlock}
  onReport={handleReport}
  size="large"
  variant="detailed"
/>

Fix 1: Pass the object:

// Better: Pass the user object
<UserCard user={user} onFollow={handleFollow} onMessage={handleMessage} />

Fix 2: Compose smaller components:

// Best: Compose from smaller, focused pieces
<UserCard user={user}>
  <UserCard.Actions>
    <FollowButton userId={user.id} />
    <MessageButton userId={user.id} />
  </UserCard.Actions>
</UserCard>

Anti-Pattern 3: Premature Abstraction

Creating reusable components before you have multiple use cases:

// BAD: Creating a "universal" DataDisplay component for one use case
function UniversalDataDisplay({
  data,
  layout,
  columns,
  sortable,
  filterable,
  paginated,
  exportable,
  theme,
  locale,
  // ... 20 more config props you might need "someday"
}) {
  // 500 lines of code handling every possible case
}

// GOOD: Start specific, generalize when patterns emerge
function UserTable({ users }) {
  // Simple, focused, does exactly what's needed now
  return (
    <table>
      <thead>
        <tr><th>Name</th><th>Email</th><th>Role</th></tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.email}</td>
            <td>{user.role}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
// When you need a ProductTable too, THEN extract common patterns

Anti-Pattern 4: Tightly Coupled Components

Components that can't function without each other:

// BAD: ProductCard reaches into parent's state
function ProductCard({ product }) {
  const { addToCart } = useCartStore();    // directly accessing global store
  const { user } = useAuthStore();         // directly accessing auth store
  const { theme } = useThemeStore();       // directly accessing theme

  // This component is now tightly coupled to 3 stores
  // Can't reuse it in a context without those stores
}

// GOOD: ProductCard is independent — receives everything via props
function ProductCard({ product, onAddToCart, isLoggedIn }) {
  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{product.price}</p>
      {isLoggedIn
        ? <button onClick={() => onAddToCart(product.id)}>Add to Cart</button>
        : <button disabled>Login to Buy</button>
      }
    </div>
  );
}

15. Real-World Component Library Design

Building a Button Component System

A well-designed Button is the cornerstone of any component library:

function Button({
  children,
  variant = 'primary',    // primary | secondary | ghost | danger
  size = 'medium',         // small | medium | large
  disabled = false,
  loading = false,
  leftIcon,
  rightIcon,
  fullWidth = false,
  type = 'button',
  onClick,
  ...rest                  // pass through any HTML button attributes
}) {
  const classNames = [
    'btn',
    `btn-${variant}`,
    `btn-${size}`,
    fullWidth && 'btn-full',
    loading && 'btn-loading',
  ].filter(Boolean).join(' ');

  return (
    <button
      className={classNames}
      disabled={disabled || loading}
      type={type}
      onClick={onClick}
      {...rest}
    >
      {loading && <Spinner size="small" />}
      {!loading && leftIcon && <span className="btn-icon-left">{leftIcon}</span>}
      <span className="btn-text">{children}</span>
      {!loading && rightIcon && <span className="btn-icon-right">{rightIcon}</span>}
    </button>
  );
}

Usage:

// All the variants
<Button variant="primary">Save Changes</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="ghost">Learn More</Button>
<Button variant="danger">Delete Account</Button>

// Sizes
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
<Button size="large">Large</Button>

// States
<Button loading>Saving...</Button>
<Button disabled>Cannot Submit</Button>

// With icons
<Button leftIcon={<PlusIcon />}>Add Item</Button>
<Button rightIcon={<ArrowIcon />}>Continue</Button>

// Full width
<Button fullWidth>Submit Application</Button>

Building a Card Component System

function Card({ children, variant = 'default', padding = 'medium', ...rest }) {
  const classNames = `card card-${variant} card-pad-${padding}`;
  return <div className={classNames} {...rest}>{children}</div>;
}

// Sub-components for compound usage
Card.Header = function CardHeader({ children, actions }) {
  return (
    <div className="card-header">
      <div className="card-header-title">{children}</div>
      {actions && <div className="card-header-actions">{actions}</div>}
    </div>
  );
};

Card.Body = function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
};

Card.Footer = function CardFooter({ children, align = 'right' }) {
  return <div className={`card-footer card-footer-${align}`}>{children}</div>;
};

// Usage — compose naturally
<Card variant="elevated">
  <Card.Header actions={<IconButton icon={<MoreIcon />} />}>
    Product Details
  </Card.Header>
  <Card.Body>
    <p>MacBook Pro 16-inch</p>
    <p>$2,499.00</p>
  </Card.Body>
  <Card.Footer>
    <Button variant="ghost">Cancel</Button>
    <Button variant="primary">Add to Cart</Button>
  </Card.Footer>
</Card>

Building a Modal Component

import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, title, children, size = 'medium' }) {
  // Don't render if not open
  if (!isOpen) return null;

  // Handle Escape key
  useEffect(() => {
    function handleEscape(e) {
      if (e.key === 'Escape') onClose();
    }
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, [onClose]);

  // Prevent body scroll when modal is open
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);

  // Render into a portal (outside the component tree's DOM)
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div
        className={`modal modal-${size}`}
        onClick={e => e.stopPropagation()} // prevent closing when clicking inside
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
      >
        <div className="modal-header">
          <h2 id="modal-title">{title}</h2>
          <button className="modal-close" onClick={onClose} aria-label="Close">
            &times;
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

// Usage
function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <Button onClick={() => setShowModal(true)}>Open Modal</Button>

      <Modal
        isOpen={showModal}
        onClose={() => setShowModal(false)}
        title="Confirm Delete"
        size="small"
      >
        <p>Are you sure you want to delete this item?</p>
        <div className="modal-actions">
          <Button variant="ghost" onClick={() => setShowModal(false)}>Cancel</Button>
          <Button variant="danger" onClick={handleDelete}>Delete</Button>
        </div>
      </Modal>
    </div>
  );
}

Component Library Design Principles

PrincipleDescriptionExample
Consistent APISimilar components have similar propsAll have variant, size, disabled
Sensible defaultsWorks well with zero config<Button>Text</Button> looks good by default
ComposableComponents work together naturallyCard + Button + Modal compose into a dialog
AccessibleARIA attributes, keyboard navigationModal has role="dialog", aria-modal
ThemeableRespects design tokens/CSS variablesColors, spacing from CSS custom properties
Extensible...rest props for custom attributesPass data-testid, aria-label, etc.

16. Key Takeaways

  1. Component-based architecture splits UI into independent, reusable pieces — this is React's fundamental mental model.

  2. Every React app is a component tree — data flows down through props, events flow up through callbacks.

  3. A component is a function that returns JSX — it can contain state, effects, handlers, and computed values.

  4. Presentational vs container is a useful mental model — modern React replaces container components with custom hooks.

  5. Break down real UIs into component trees before writing code — this is the most important React skill.

  6. Split components when they violate single responsibility, exceed ~100 lines, contain reusable patterns, or have isolatable complexity.

  7. Name components with PascalCase, be descriptive, and keep naming consistent across the project.

  8. Choose a file organization strategy that matches your project size — type-based for small, feature-based for large.

  9. Composition beats inheritance — use children, slots, compound components, and render props.

  10. Components communicate through props (down), callbacks (up), lifted state (siblings), and context (distant).

  11. Start specific, generalize later — premature abstraction is worse than duplication.

  12. Avoid anti-patterns: god components, prop explosion, premature abstraction, tight coupling.

  13. A good component library has consistent APIs, sensible defaults, and follows accessibility standards.


Explain-It Challenge

  1. The Dinner Party Analogy: Imagine you're organizing a large dinner party. Explain how component-based architecture is like dividing responsibilities among different people (chef, decorator, DJ, host) rather than having one person do everything. What happens when the "god person" approach fails?

  2. Teach Component Communication: A friend asks "How do React components talk to each other?" Explain the three main patterns (props down, callbacks up, lifting state) using a real-world analogy — like a company where the CEO (parent) gives instructions to department heads (children), and department heads report results back up.

  3. The Reusability Decision: You're building an e-commerce site and need a product card. Your teammate wants to immediately build a "universal card component" that handles products, users, articles, and notifications. Explain why starting specific and generalizing later is usually the better approach, using the concept of the reusability spectrum.


Navigation: ← 2.1.g — JSX Syntax Rules · Next → Exercise Questions