Skip to main content

βš›οΈ React Component Architecture

React turns a web page into a tree of small, self-contained building blocks called components. In this lesson you'll learn how those blocks are defined, how they receive data, how they nest, and why the component model makes big interfaces surprisingly easy to reason about.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what a React component is and write one as a modern function component
  • Describe the difference between props (data in) and state (data owned) and how unidirectional data flow works
  • Explain the Virtual DOM and reconciliation at a high level, and why React updates the UI efficiently
  • Compose an interface from a tree of components and apply the children and specialized-component patterns
  • Organize components into a maintainable project structure

Estimated Time: 35–45 minutes  β€’  Difficulty: Intermediate

Hands-on: Sketch and build a component tree for a blog post with a live comment counter.

In This Lesson

What Is React?

React is a JavaScript library for building user interfaces, created at Facebook (now Meta) and open-sourced in 2013. It is especially strong for applications where the screen changes constantly in response to user actions or new data β€” dashboards, feeds, chat apps, editors. Instead of manually finding elements and updating them, you describe what the UI should look like for a given set of data, and React figures out the changes needed to make the real page match.

πŸ’‘ The LEGO analogy. A React component is like a LEGO brick: small, self-contained, and reusable. You snap simple bricks together to make bigger structures, and those into a whole model. The same brick can appear many times, and swapping one out never disturbs the rest. That is exactly how you build a React app β€” from many small components composed into one tree.

Three ideas define React's philosophy, and everything else in this module builds on them:

  • Declarative: you describe what the UI should be for the current data, not the step-by-step DOM instructions to get there. React handles the "how."
  • Component-based: UIs are built from encapsulated components that manage their own markup and behavior, then composed together.
  • Unidirectional data flow: data flows downward from parent to child, which makes an app far easier to trace and debug.

πŸ“– Library, not framework

React is deliberately a library focused on the view layer. It does not dictate routing, data fetching, or build tooling β€” you assemble those from the ecosystem. That is why "learn once, write anywhere" holds: the same mental model powers React DOM on the web and React Native on mobile.

Components: The Building Blocks

A component is a JavaScript function that returns markup describing a piece of UI. That markup is written in JSX β€” an HTML-like syntax you'll study in depth in the next lesson. A component's name must start with a capital letter so React can tell your components apart from built-in HTML tags.

A React application as a component tree An App component at the top branches into Header, Main, and Footer components, which branch further into smaller components, forming a tree. <App /> <Header /> <Main /> <Footer /> <Logo /> <Nav /> <Article /> <Sidebar />
Figure 1 β€” Every React app is a tree. One root component nests others, which nest still smaller ones. Data flows down the branches; events bubble decisions back up.

A modern function component

Since React 16.8 (2019), function components with Hooks are the standard. A function component is just a function that returns JSX:

// Greeting.jsx β€” the simplest useful component
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

export default Greeting;

The { name } in the parameter list destructures the incoming props object, so you can use name directly. Here are the common ways to write the same component β€” pick the one your team prefers and stay consistent:

// Function declaration
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Arrow function with an explicit return
const Greeting = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

// Arrow function with an implicit return (concise)
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;

Adding interactivity with a Hook

Components can hold their own memory using the useState Hook. Here is the classic counter β€” a component that re-renders whenever its state changes:

import { useState } from 'react';

function Counter() {
  // useState returns [currentValue, updaterFunction]
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

⚠️ Class components still exist β€” but reach for functions

You will meet older codebases using class Counter extends React.Component with this.state and lifecycle methods like componentDidMount. They still work, but the official React docs now teach function components with Hooks first. Learn the function style; recognize the class style when you see it in legacy code.

Props, State & Data Flow

Two kinds of data drive every component. Getting the distinction right is the single most important concept in this module.

PropsState
Who owns itThe parent componentThe component itself
Can it change?Read-only inside the childYes, via its setter (e.g. setCount)
DirectionFlows down, parent β†’ childLocal; triggers a re-render when updated
AnalogyArguments passed to a functionA variable the function remembers between calls

Passing props looks just like setting HTML attributes, but the values can be any JavaScript expression inside { }:

function App() {
  return (
    <UserProfile
      name="Alice"
      role="Admin"
      isActive={true}
      loginCount={42}
    />
  );
}

function UserProfile({ name, role, isActive, loginCount }) {
  return (
    <div className="user-profile">
      <h2>{name}</h2>
      <p>Role: {role}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
      <p>Logins: {loginCount}</p>
    </div>
  );
}

Unidirectional data flow

Data always travels one direction: down. A parent hands props to its children; children never reach up and mutate the parent directly. When a child needs to affect the parent, the parent passes down a callback function as a prop, and the child calls it β€” sending information back up through a controlled channel.

flowchart TD A["Parent (owns state)"] -->|"props: value"| B[Child A] A -->|"props: value + onChange callback"| C[Child B] C -.->|"calls onChange(newValue)"| A A -->|"re-render with new value"| B

This is why React apps are predictable: to understand any piece of the screen, you trace props down from where the data lives. There is one source of truth, and it flows outward.

πŸ“– "Lifting state up"

When two sibling components need the same data, you move (lift) that state into their nearest common parent, then pass it down to both as props. You'll use this pattern constantly. Global sharing across a deep tree gets its own tool β€” the Context API β€” later in this module.

The Virtual DOM

Directly touching the real browser DOM is slow, and doing it by hand for every little change is error-prone. React's answer is the Virtual DOM: a lightweight JavaScript representation of what the UI should look like.

When state changes, React builds a fresh virtual tree, diffs it against the previous one (a process called reconciliation), and then makes only the minimal set of real DOM changes needed. You describe the destination; React computes the cheapest route.

flowchart LR A[State changes] --> B[Build new Virtual DOM] B --> C{Diff against
previous tree} C -->|Only what changed| D[Patch the real DOM] D --> E[Browser repaints]
πŸ’‘ Analogy. Imagine editing a printed document. Rather than reprinting every page for one typo (updating the whole DOM), you mark just the changed line and reprint only that page. The Virtual DOM is React's markup of exactly which lines changed.

This is also why keys matter when rendering lists: a stable, unique key prop helps the diffing algorithm match items across renders so it can tell what was added, removed, or reordered. You'll see keys in action in the next lesson.

Composition Patterns

React favors composition over inheritance: you build complex UIs by nesting and combining simple components, not by extending classes. A few reusable patterns cover the vast majority of real code.

1. Basic composition

Split a page into named pieces, each responsible for one area:

function App() {
  return (
    <div className="app">
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

2. The children prop

Any content you place between a component's opening and closing tags arrives as the special children prop. This is how you build flexible wrappers like cards, modals, and layouts:

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

// The Card doesn't care what's inside β€” it just renders whatever you pass.
function ProfileCard() {
  return (
    <Card title="User Profile">
      <h3>Jane Doe</h3>
      <p>Frontend Developer</p>
      <button>Edit Profile</button>
    </Card>
  );
}

3. Specialized components

Configure a general component with fixed props to create purpose-built variants. The spread operator ({...props}) forwards any remaining props through:

function Button({ variant = 'primary', children, ...rest }) {
  return (
    <button className={`btn btn-${variant}`} {...rest}>
      {children}
    </button>
  );
}

const PrimaryButton  = (props) => <Button variant="primary" {...props} />;
const DangerButton   = (props) => <Button variant="danger" {...props} />;

βœ… Presentational vs. container thinking

A useful mental split: presentational components focus on how things look (they take props, render markup, rarely hold state), while container components focus on how things work (they fetch data and manage state, then hand it to presentational children). Keeping the two roles distinct makes components easier to test and reuse.

Design systems formalize this further with Atomic Design β€” atoms (Button, Input) compose into molecules (SearchBar), then organisms (Header), templates, and pages. Airbnb, IBM's Carbon, and Material UI all use a hierarchy like this to keep large UIs consistent.

Organizing Components

As an app grows, a predictable folder structure keeps it navigable. A widely used convention groups components by role and colocates each component's related files:

src/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ common/          # Reusable anywhere (Button, Card, Modal)
β”‚   β”‚   └── Button/
β”‚   β”‚       β”œβ”€β”€ Button.jsx
β”‚   β”‚       β”œβ”€β”€ Button.test.jsx
β”‚   β”‚       └── Button.module.css
β”‚   β”œβ”€β”€ layout/          # Header, Footer, Sidebar
β”‚   └── features/        # Feature-specific (UserProfile, Dashboard)
β”œβ”€β”€ hooks/               # Custom reusable Hooks (useAuth, useFetch)
β”œβ”€β”€ context/             # Context providers for shared state
β”œβ”€β”€ pages/               # Top-level route components
└── utils/               # Plain helper functions

The payoff: components are easy to find, common and feature code stay separated, and each component travels with its test and styles. You do not need this much structure on day one β€” start simple and grow into it as the app earns the complexity.

Hands-on Exercise

πŸ‹οΈ Build a Blog Post Component Tree

Objective: Practice decomposing a UI into components and passing data with props.

Instructions:

  1. On paper or in a comment, sketch a component tree for a blog post that shows: a header (title, author, date), the body content, a tag list, and a comments section.
  2. Build BlogPost as the container. Give it these child components: PostHeader, PostBody, TagList, and CommentSection.
  3. Pass data down as props. Render the tags and comments with .map(), giving each item a unique key.
  4. In CommentSection, add a useState counter that shows how many comments there are.

Use this sample data:

const post = {
  title: 'Getting Started with React',
  author: 'Jane Smith',
  date: '2026-02-10',
  tags: ['React', 'JavaScript', 'Frontend'],
  comments: [
    { id: 1, author: 'John', text: 'Great intro!' },
    { id: 2, author: 'Alice', text: 'Very helpful, thanks.' }
  ]
};
πŸ’‘ Hint

Start with the container and stub the children as components that just return a heading. Get the tree rendering first, then fill in each child. For the tag list: {tags.map(t => <li key={t}>{t}</li>)}.

βœ… Sample solution
function BlogPost({ post }) {
  return (
    <article>
      <PostHeader title={post.title} author={post.author} date={post.date} />
      <PostBody />
      <TagList tags={post.tags} />
      <CommentSection comments={post.comments} />
    </article>
  );
}

function PostHeader({ title, author, date }) {
  return (
    <header>
      <h1>{title}</h1>
      <p>By {author} Β· {date}</p>
    </header>
  );
}

function PostBody() {
  return <p>React makes it painless to build interactive UIs…</p>;
}

function TagList({ tags }) {
  return (
    <ul>
      {tags.map((tag) => <li key={tag}>{tag}</li>)}
    </ul>
  );
}

function CommentSection({ comments }) {
  const [count] = useState(comments.length);
  return (
    <section>
      <h2>{count} Comments</h2>
      {comments.map((c) => (
        <div key={c.id}><strong>{c.author}:</strong> {c.text}</div>
      ))}
    </section>
  );
}

Best Practices

βœ… Do

  • Keep components small and focused β€” one clear responsibility each.
  • Name components with a capital letter and in PascalCase (UserCard).
  • Treat props as read-only; never mutate them inside a child.
  • Give list items a stable, unique key (a database id, not the array index when the list can reorder).
  • Prefer composition (nesting, children) over duplicating markup.

⚠️ Don't

  • Don't build one giant component β€” if you're scrolling to understand it, split it.
  • Don't reach across the tree to change a parent's data directly; pass a callback prop instead.
  • Don't over-drill props through many layers. If data travels far, that's a signal to lift state or use Context.
  • Don't use the array index as a key for lists that can be reordered, inserted into, or filtered.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • A React app is a tree of components β€” small functions that return JSX.
  • Props flow down (read-only inputs); state is a component's own changeable memory.
  • Data flow is unidirectional; children talk back to parents through callback props.
  • The Virtual DOM lets React apply only the minimal real-DOM changes after each update.
  • Build UIs by composition: nesting, the children prop, and specialized components.

🎯 Quick Quiz

Question 1: What is the key difference between props and state?

Question 2: Why does React use a Virtual DOM?

Question 3: A child component needs to notify its parent that a button was clicked. What's the idiomatic React approach?

πŸ“š Further Reading

πŸš€ What's Next?

You now have the shape of a React app in your head: a tree of components exchanging props. Next we'll zoom into the syntax that makes those components readable β€” JSX β€” and go deep on how props actually work.

πŸŽ‰ Great start!

The component model is the foundation everything else in this module stands on.