βοΈ 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 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.
| Props | State | |
|---|---|---|
| Who owns it | The parent component | The component itself |
| Can it change? | Read-only inside the child | Yes, via its setter (e.g. setCount) |
| Direction | Flows down, parent β child | Local; triggers a re-render when updated |
| Analogy | Arguments passed to a function | A 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.
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.
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:
- 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.
- Build
BlogPostas the container. Give it these child components:PostHeader,PostBody,TagList, andCommentSection. - Pass data down as props. Render the tags and comments with
.map(), giving each item a uniquekey. - In
CommentSection, add auseStatecounter 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
keyfor 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
childrenprop, 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
- React docs β Your First Component
- React docs β Thinking in React
- React docs β Passing Props to a Component
- Atomic Design by Brad Frost
π 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.