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.
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:
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>
);
}
this keyword confusion.useState, useEffect) for state and lifecycle management.
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>
);
}
}
this, and complex lifecycle methods (componentDidMount, componentDidUpdate).ErrorBoundary components).| 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) |
React enforces a strict grammatical rule regarding component identifiers:
All React components must start with a capital letter (PascalCase):
• Valid Component Names: UserProfile, NavigationBar, PrimaryButton, ModalDialog.
• Invalid Component Names: userProfile, navigationBar, button.
When JSX compiles into React.createElement(type), React checks the first character of the tag:
<button> or <div>), React compiles it as a string: React.createElement("button"). The browser renders the built-in HTML DOM element.<Button>), React compiles it as an in-scope variable identifier: React.createElement(Button). React executes your custom JavaScript component function.Professional React codebases separate components into dedicated files for modularity, unit testing, and team collaboration.
Standard for primary components (e.g., pages, modal overlays, main screens):
// 1. Defining and exporting by default
export default function Header() {
return (
<header className="main-header">
<h1>DevAscent Academy</h1>
</header>
);
}
// 2. Importing without curly braces
import Header from "./components/Header.jsx";
export default function App() {
return (
<div>
<Header />
</div>
);
}
Standard for design systems containing multiple related UI primitives in one file:
// 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>;
}
// 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>
);
}
In Object-Oriented Programming (OOP), developers often reuse code by creating inheritance hierarchies (e.g., SpecialButton extends Button).
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.
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:
// 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>
);
}
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.
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.
As React applications grow from dozens to hundreds of components, organizing files into a scalable, predictable architecture is essential.
Props (short for properties) are the mechanism by which parent components pass data downward to their child components.
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:
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:
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.Below is a clean, modular React component architecture demonstrating component composition, reusable UI elements with props and children, and clean prop forwarding:
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>
);
}
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).
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.
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.
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:
children prop to the top-level container, removing intermediate pass-through layers.useContext hook without passing props through intermediate nodes.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.
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.
children prop for container containment.