⚛️ Curriculum Track • Frontend Engineering & React

Unit 4: Components & Props

Architectural foundations of React components: functional components, legacy class components, PascalCase naming conventions, exporting and importing modules, component composition over inheritance, reusable component design patterns, nested hierarchies, scalable enterprise folder structures, and resolving the prop drilling dilemma.

📚 Module: Unit 4 of 12 Core Units
🧩 Design Paradigm: Modular Component Composition
📦 Data Flow: Unidirectional Props & Component Inversion
📖 Format: 100% Pure Natural English (Zero LaTeX)
01

Functional Components vs. Legacy Class Components

In React, a Component is an autonomous, self-contained, and reusable piece of user interface that accepts inputs (called props) and returns a hierarchy of React elements describing what should appear on the screen. Over React's history, two primary paradigms emerged:

1. Modern Functional Components (Standard)

A pure JavaScript function that accepts a single props object argument and returns JSX markup:

// Modern Functional Component
export function UserCard({ name, role }) {
  return (
    <div className="user-card">
      <h3>{name}</h3>
      <p>{role}</p>
    </div>
  );
}
  • Concise & Clean: Zero boilerplate code, no this keyword confusion.
  • Powered by Hooks: Uses modern React Hooks (useState, useEffect) for state and lifecycle management.
  • Official Standard: 100% of modern React documentation and production codebases use functional components.
2. Class Components (Legacy)

ES6 classes extending React.Component with a mandatory render() method:

// Legacy Class Component
import React, { Component } from "react";

export class UserCard extends Component {
  render() {
    return (
      <div className="user-card">
        <h3>{this.props.name}</h3>
        <p>{this.props.role}</p>
      </div>
    );
  }
}
  • Heavy Boilerplate: Requires constructors, binding this, and complex lifecycle methods (componentDidMount, componentDidUpdate).
  • Maintained for Backward Compatibility: Still found in older codebases, but rarely authored in new projects (except for specialized ErrorBoundary components).

Detailed Architectural Comparison

Evaluation Factor Functional Components Class Components
Declaration Syntax Plain JavaScript function / Arrow function ES6 Class extending React.Component
State Management React useState & useReducer Hooks this.state object and this.setState()
Lifecycle Execution useEffect Hook componentDidMount, componentDidUpdate, componentWillUnmount
"this" Keyword Not used; uses standard lexical scoping Mandatory; requires careful method binding in constructor
Code Reusability Custom Hooks (clean and composable) Higher-Order Components (HOCs) and Render Props (verbose)
02

Component Naming Conventions

React enforces a strict grammatical rule regarding component identifiers:

⚠️ Mandatory PascalCase Rule

All React components must start with a capital letter (PascalCase):

• Valid Component Names: UserProfile, NavigationBar, PrimaryButton, ModalDialog.
• Invalid Component Names: userProfile, navigationBar, button.

Why React Requires PascalCase

When JSX compiles into React.createElement(type), React checks the first character of the tag:

03

Creating, Exporting & Importing Components

Professional React codebases separate components into dedicated files for modularity, unit testing, and team collaboration.

Strategy A: Default Exports (Single Component per File)

Standard for primary components (e.g., pages, modal overlays, main screens):

src/components/Header.jsx
// 1. Defining and exporting by default
export default function Header() {
  return (
    <header className="main-header">
      <h1>DevAscent Academy</h1>
    </header>
  );
}
src/App.jsx (Consuming Component)
// 2. Importing without curly braces
import Header from "./components/Header.jsx";

export default function App() {
  return (
    <div>
      <Header />
    </div>
  );
}
Strategy B: Named Exports (Component Libraries)

Standard for design systems containing multiple related UI primitives in one file:

src/components/Buttons.jsx
// 1. Multiple named exports in a single file
export function PrimaryButton({ label, onClick }) {
  return <button className="btn-primary" onClick={onClick}>{label}</button>;
}

export function SecondaryButton({ label, onClick }) {
  return <button className="btn-secondary" onClick={onClick}>{label}</button>;
}
src/App.jsx (Consuming Component)
// 2. Importing with curly braces
import { PrimaryButton, SecondaryButton } from "./components/Buttons.jsx";

export default function App() {
  return (
    <div>
      <PrimaryButton label="Save" />
      <SecondaryButton label="Cancel" />
    </div>
  );
}
04

Component Composition: Composition Over Inheritance

In Object-Oriented Programming (OOP), developers often reuse code by creating inheritance hierarchies (e.g., SpecialButton extends Button).

📌 The React Philosophy: Composition Over Inheritance

React has never recommended inheritance hierarchies for UI components. At Meta, thousands of components were built across Facebook and Instagram without using a single class inheritance chain!

Instead, React achieves extreme reusability through Composition: assembling complex components by combining simpler, specialized components and nesting them using the special children prop.

Containment & The Special children Prop

Some components don't know their child content ahead of time (such as generic cards, sidebars, dialogue boxes, or layouts). These components accept the children prop:

Reusable Card Shell with Children Composition
// Generic Container Component
function CardShell({ title, variant = "default", children }) {
  return (
    <div className={`card-shell card-${variant}`}>
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      {/* The children prop renders whatever is placed between opening & closing tags */}
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Consuming Component Composing Different Contents Inside the Same Shell
export default function Dashboard() {
  return (
    <div className="dashboard-grid">
      {/* Composition 1: Text and button */}
      <CardShell title="Storage Quota">
        <p>You have used 85% of your 100GB enterprise storage.</p>
        <button>Upgrade Plan</button>
      </CardShell>

      {/* Composition 2: Form controls */}
      <CardShell title="Security Settings" variant="warning">
        <label>
          <input type="checkbox" defaultChecked /> Enable Two-Factor Auth
        </label>
      </CardShell>
    </div>
  );
}
05

Nested Components & Hierarchical Trees

React applications form a tree of nested components. The top-level App component branches into feature components, which in turn branch into atomic UI primitives.

+--------------------------------------------------------------------------+ | REAL-WORLD NESTED COMPONENT TREE | +--------------------------------------------------------------------------+ [ App ] | +---------------------+---------------------+ | | [ Navigation ] [ ProductFeed ] | | +--------+--------+ +--------+--------+ | | | | [ BrandLogo ] [ UserMenu ] [ SearchBar ] [ ProductGrid ] | +------+------+ | | [ ItemCard ] [ ItemCard ] | | [ BuyButton ] [ BuyButton ]
⚠️ Critical Anti-Pattern: Never Nest Component Definitions!

You should freely nest component instances in JSX. However, you must never define a component function inside another component function:

// DANGEROUS ANTI-PATTERN: DO NOT DO THIS!
function ParentComponent() {
  // Every time ParentComponent re-renders, ChildComponent is re-created from scratch!
  function ChildComponent() {
    return <div>Sub-item</div>;
  }

  return <ChildComponent />;
}

Defining components inside other components forces React to treat the child as a brand-new component type on every single re-render, completely destroying its state, resetting DOM focus, and causing severe performance degradation. Always define components at the top-level scope of a module.

06

Enterprise Component Folder Structure

As React applications grow from dozens to hundreds of components, organizing files into a scalable, predictable architecture is essential.

The Feature-Based Folder Architecture

src/ |-- assets/ # Global static assets (SVGs, logos, fonts) |-- components/ # Shared, atomic, reusable UI components (Buttons, Modals, Inputs) | |-- Button/ | | |-- Button.jsx | | |-- Button.module.css | | +-- index.js # Barrel export: export * from "./Button"; | +-- Modal/ |-- features/ # Business domain feature modules (Auth, Checkout, Products) | |-- auth/ | | |-- components/ # Components specific to authentication | | | +-- LoginForm.jsx | | |-- hooks/ # Custom hooks specific to auth | | +-- services/ # API request helpers | +-- checkout/ |-- hooks/ # Global reusable custom hooks |-- layouts/ # Page scaffolding (Navbar, Sidebar, Footer wrappers) |-- pages/ # Route-level page components (HomePage.jsx, ProfilePage.jsx) |-- utils/ # Pure helper functions (formatters, validators) |-- App.jsx # Root component (routing configuration) +-- main.jsx # Entry point mounting to the real DOM
07

Props & The Prop Drilling Dilemma

Props (short for properties) are the mechanism by which parent components pass data downward to their child components.

Key Characteristics of Props

What is Prop Drilling?

Prop Drilling refers to the process of passing props down through multiple layers of intermediate components that do not need the data themselves, solely to deliver it to a deeply nested child:

+--------------------------------------------------------------------------+ | THE PROP DRILLING BOTTLENECK | +--------------------------------------------------------------------------+ [ App ] (Holds 'currentUser' state) | |-- passes 'currentUser' prop v [ Layout ] (Does NOT need 'currentUser'; purely passes it down) | |-- passes 'currentUser' prop v [ Header ] (Does NOT need 'currentUser'; purely passes it down) | |-- passes 'currentUser' prop v [ UserAvatar ] (FINALLY uses 'currentUser' to display avatar photo!)
How Industry Resolves Prop Drilling

Passing props through 1 or 2 levels is completely normal and encouraged for explicit data flow. However, when props are drilled through 4 or more intermediate components, maintenance becomes painful. Modern React solves this through two primary solutions:

  • Component Inversion (Children Composition): Instead of having Layout render Header which renders UserAvatar, pass UserAvatar directly as a child to Layout! This allows App to pass props directly to UserAvatar without intermediate intermediaries.
  • React Context API (Unit 9): Teleports state directly from a top-level provider to any consuming child component anywhere in the tree, completely bypassing intermediate levels.
08

Complete Standalone Practical Implementation

Below is a clean, modular React component architecture demonstrating component composition, reusable UI elements with props and children, and clean prop forwarding:

src/ComponentArchitectureDemo.jsx • Modular Production Pattern
import { useState } from "react";

// Reusable Atomic Primitive: Badge
function Badge({ variant = "info", children }) {
  const styles = {
    info: { bg: "#e0f2fe", text: "#0369a1" },
    success: { bg: "#dcfce7", text: "#15803d" },
    warning: { bg: "#fef3c7", text: "#b45309" }
  };
  const activeStyle = styles[variant] || styles.info;

  return (
    <span style={{
      backgroundColor: activeStyle.bg,
      color: activeStyle.text,
      padding: "2px 8px",
      borderRadius: "9999px",
      fontSize: "11px",
      fontWeight: 700
    }}>
      {children}
    </span>
  );
}

// Reusable Layout Container with Composition (Children)
function ProfileCard({ name, role, isVerified, children }) {
  return (
    <div style={{
      border: "1px solid #e2e8f0",
      borderRadius: "12px",
      padding: "20px",
      background: "#ffffff",
      boxShadow: "0 1px 3px rgba(0,0,0,0.05)",
      maxWidth: "380px"
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "8px" }}>
        <h3 style={{ margin: 0, fontSize: "16px", color: "#0f172a" }}>{name}</h3>
        {isVerified && <Badge variant="success">Verified</Badge>}
      </div>
      <p style={{ margin: "0 0 16px 0", color: "#64748b", fontSize: "13px" }}>{role}</p>

      {/* Composed dynamic children content */}
      <div style={{ borderTop: "1px solid #f1f5f9", paddingTop: "12px" }}>
        {children}
      </div>
    </div>
  );
}

// Root Application Component Demonstrating Composition
export default function ComponentArchitectureDemo() {
  const [endorsements, setEndorsements] = useState(12);

  return (
    <div style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <h2 style={{ color: "#0f172a" }}>Team Member Directory</h2>

      <ProfileCard
        name="Alex Rivera"
        role="Principal Frontend Architect"
        isVerified={true}
      >
        <p style={{ fontSize: "12.5px", color: "#334155", lineHeight: 1.5 }}>
          Specializes in large-scale React design systems and performance optimization.
        </p>
        <button
          onClick={() => setEndorsements(endorsements + 1)}
          style={{
            background: "#0284c7",
            color: "#ffffff",
            border: "none",
            borderRadius: "6px",
            padding: "6px 12px",
            fontSize: "12px",
            fontWeight: 600,
            cursor: "pointer",
            marginTop: "8px"
          }}
        >
          Endorse Skills ({endorsements})
        </button>
      </ProfileCard>
    </div>
  );
}
09

Academic Viva Voce & Technical Interview Masterclass

Q1: Why does React require component function names to be written in PascalCase?

Answer: The JSX compiler differentiates between native HTML DOM elements and custom user-defined components based on capitalization. If a tag begins with a lowercase letter (e.g., <div> or <button>), the compiler treats it as a built-in HTML tag string passed directly to React.createElement("div"). If a tag begins with an uppercase letter (e.g., <Button>), the compiler treats it as a JavaScript function or class identifier in the current scope passed to React.createElement(Button).

Q2: What is the principle of "Composition over Inheritance", and why does React advocate it?

Answer: In object-oriented programming, inheritance binds classes into rigid hierarchical parent-child relationships that become brittle as systems evolve. React advocates Composition: building complex user interfaces by combining small, focused, and autonomous components together using props and the special children prop. Composition provides maximum flexibility, allowing components to encapsulate layout or styling shells without knowing or dictating the specific content rendered inside them.

Q3: Why must React props be strictly read-only and immutable?

Answer: React follows a pure functional programming philosophy: components must act as pure functions with respect to their props. If a child component were allowed to mutate its incoming props directly, it would cause side-effects that violate unidirectional data flow, making state changes unpredictable and impossible to trace across the component hierarchy. If prop data must change, the owning parent component must update its state and pass the new values downward.

Q4: What is "Prop Drilling", and what are the two primary ways to resolve it?

Answer: Prop drilling is the antipattern of passing props through several layers of intermediate components that do not consume the data themselves, solely to deliver it to a deeply nested target component. The two primary solutions are:

  1. Component Inversion / Composition: Passing the leaf component directly as a children prop to the top-level container, removing intermediate pass-through layers.
  2. React Context API: Providing global or sub-tree state that any deeply nested child can consume directly using the useContext hook without passing props through intermediate nodes.

Q5: Why is defining a component function inside another component function considered a critical performance bug?

Answer: When Component B is defined inside Component A's body, every time Component A re-renders, a completely new function reference for Component B is created in memory. During the reconciliation diffing phase, React checks component identity: seeing a brand-new function reference, React concludes that the old component was deleted and an entirely new component type was mounted. It unmounts the old DOM tree, destroys all internal state and timers, resets input cursor focus, and mounts a fresh element on every render, severely degrading performance.

Q6: What is a "Barrel Export" in component folder architecture?

Answer: A barrel export is an index.js (or index.ts) file located inside a component directory that rolls up and re-exports exports from adjacent component files: export * from "./Button"; export * from "./Modal";. This enables consuming components to import multiple UI primitives from a clean single directory import path (import { Button, Modal } from "@/components";) rather than writing long, individual file paths.

10

Unit Summary & Key Conceptual Takeaways

Core Principles to Retain
  • Functional Components are the Standard: Clean, stateless functions enhanced by React Hooks have completely replaced legacy ES6 class components.
  • PascalCase is Mandatory: Lowercase tags denote native browser HTML elements; PascalCase tags identify custom React components.
  • Composition Over Inheritance: Assemble complex user interfaces by combining autonomous primitives and using the children prop for container containment.
  • Never Define Components Inside Components: Always define component functions at the top-level module scope to prevent re-creation and state loss on re-renders.
  • Props are Immutable: Props flow strictly downwards in one direction; components must never modify their own props.
  • Mitigate Prop Drilling: Use component composition or the Context API to bypass multi-layer intermediate prop tunnels.