You closed out Module 1 knowing how React turns your functions into a screen: JSX compiles into objects, those objects form a tree, and reconciliation decides what to touch in the real DOM. Now it's time to move up a level and look at the problem from a design angle, not from the engine's. A component isn't just "a function that returns JSX": it's your application's unit of design, the box where you decide which markup, which logic, and which style live together. This lesson is about the most frequent and most consequential decision you'll make in React: where to draw the dividing lines. We'll start from the real sketch of CicloUrbano's catalogue screen, break it down into components with judgment, draw its hierarchy, and organize the files so that, twenty components from now, you're still finding what you're looking for.
Contents
- What a component actually encapsulates
- From sketch to components: CicloUrbano's catalogue screen
- Three criteria for deciding a component's boundaries
- The component tree: parent-child relationships
- Presentation components and container components
- File organization and naming conventions
- Default export vs named export
- Composing: assembling the full screen
- The limitation we're still carrying: the data is still fixed
- What a component actually encapsulates
In Module 1 you wrote Welcome and BikeCard as functions that return JSX. That's the mechanics. The underlying idea is something else: a component groups the three things that define a piece of interface into a single place.
| What it encapsulates | In CicloUrbano, inside BikeCard |
|---|---|
| Markup | The <article>, the <h3> with the model, the <p>s with type, status, and price |
| Logic | How the price gets formatted, which text corresponds to each status, whether the book button shows or not |
| Style | The bike-card class and the variants that depend on the bike's type |
For years, best practice in web development was exactly the opposite: HTML in one file, CSS in another, JavaScript in a third. React proposes separating by functional concern instead of by technology. The reasoning is simple: when someone asks you to "change how a bike looks in the catalogue," you don't touch "the CSS" or "the JavaScript," you touch the bike card. Having everything that defines the card in view in a single file is what makes the change fast and safe.
This has three practical consequences worth internalizing right away:
- A component is replaceable. If
BikeCardhonors its contract, you can rewrite it entirely without the rest of the application ever knowing. - A component is reusable. The same component works in the catalogue, in a station's detail view, and on the bookings screen.
- A component is reasonable in isolation. You can understand what
BikeCarddoes without readingApp.jsx. When that stops being true, it's almost always because the component is doing too much.
- From sketch to components: CicloUrbano's catalogue screen
Here's the sketch of the screen we're going to build over the next few modules: CicloUrbano's bike catalogue.
┌──────────────────────────────────────────────────────────┐ │ CicloUrbano │ │ Rent an urban bike in your district… │ │ Catalogue · Stations · My bookings │ ├──────────────────────────────────────────────────────────┤ │ Available bikes │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Classic Urban [ disponible ] │ │ │ │ urbana · Main Square · €2.50 / hour │ │ │ └────────────────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Electric Pro [ alquilada ] │ │ │ │ electrica · Main Square · €4.00 / hour │ │ │ └────────────────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Cargo Max [ mantenimiento ] │ │ │ │ carga · North Park · €5.50 / hour │ │ │ └────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────┤ │ CicloUrbano — Bike rental service │ │ Stations: Main Square · North Park · Central Station │ └──────────────────────────────────────────────────────────┘
The method for breaking it apart is always the same: draw a rectangle around every zone that has a name of its own and check whether that name describes a responsibility. Applying it yields five components:
| Component | Zone of the sketch | Responsibility |
|---|---|---|
Header |
Top strip | Site identity and main navigation |
BikeList |
Central section | Presenting the set of bikes with its title |
BikeCard |
Each box | Showing one bike |
StatusBadge |
The [ disponible ] badge |
Turning a status into a visual badge |
Footer |
Bottom strip | Legal and contact information |
Notice two decisions that aren't obvious:
BikeListexists even though right now it looks like a plain container. We give it its own identity because later it will host the type and status filters and the "no results" message. It's the zone that's going to grow, and already having its own box keeps that growth from spilling over intoApp.StatusBadgeexists even though it's just a<span>. The badge will also show up in the station detail view and in the bookings list, always with the same color rules. A two-line component used in three places is more than justified.
- Three criteria for deciding a component's boundaries
There's no mechanical rule, but there are three criteria that, applied in this order, resolve almost every case.
Criterion 1: single responsibility
You should be able to describe a component in one sentence without the word "and."
- ✅ "
BikeCardshows the data for one bike." - ❌ "
Catalogueshows the header and filters the bikes and paints the cards and manages the footer."
When the description needs conjunctions, you have a candidate for splitting. Watch out for the opposite false positive: splitting so much that every component is an HTML tag in disguise. A <SectionTitle> that just returns <h2> doesn't add anything.
Criterion 2: reuse
If a piece appears — or is going to appear — in more than one place with the same look and the same rules, it's a component. This criterion is what justifies StatusBadge: the mapping from disponible to green and mantenimiento to red is a decision we want written exactly once. If it were copied across three screens, the day the palette changes you'd have to remember all three spots.
Watch out for the reverse mistake: don't create a reusable component before you have the second use case. Designing for imaginary reuse produces components with twisted contracts that end up not fitting the second real case.
Criterion 3: manageable size
There's no magic number of lines, but there are reliable signs a component has outgrown its size:
| Warning sign | What it usually means |
|---|---|
| Doesn't fit on one editor screen | It's doing more than one thing |
| JSX nests more than four or five levels | There's an inner section that deserves its own component |
| It's hard to name | The boundaries are drawn wrong |
| Changing something touches unrelated parts | Mixed responsibilities |
And one sign that you've split things up too much: to understand a simple screen you have to open seven files and jump between them. Excessive fragmentation is technical debt too.
A fourth criterion that's coming later
When you study state (lesson Component State) a very powerful additional criterion will show up: what changes together, lives together. Parts of the interface that update at the same time usually belong to the same component. For now, stick with the first three.
- The component tree: parent-child relationships
When a component uses another one inside its JSX, a parent → child relationship is established. The set of all those relationships forms the component tree, which is your application's mental map.
flowchart TD
APP[App] --> CAB[Header]
APP --> LIS[BikeList]
APP --> PIE[Footer]
CAB --> BIE[Welcome]
LIS --> T1[BikeCard]
LIS --> T2[BikeCard]
LIS --> T3[BikeCard]
T1 --> E1[StatusBadge]
T2 --> E2[StatusBadge]
T3 --> E3[StatusBadge]
Three important clarifications about this diagram:
- Parent and child isn't inheritance.
BikeCarddoesn't inherit anything fromBikeList; it just appears inside its JSX. React doesn't use inheritance between components, it uses composition, and that contrast is covered in depth in Composition vs Inheritance. - The same component appears as many times as it's used. There are three
BikeCardnodes in the tree because there are three bikes on screen. Each one is an independent instance: they share the code, not the data or the state. - Information flows down the tree. Data flow in React is unidirectional: from parent to child. That's exactly the path props will travel, in the lesson Props.
This tree is also what you'll see in the Components tab of React DevTools, the extension you installed in lesson 01-02. Keep it open while you build: comparing the tree you sketched with the one React shows is the fastest way to catch that your mental structure and your code have drifted apart.
- Presentation components and container components
Over time you'll notice your components naturally fall into two families.
| Presentation components | Container components | |
|---|---|---|
| What they're for | Painting something | Deciding what gets painted |
| Where they get their data from | Given to them from outside | They fetch, filter, or sort it |
| Have state | Usually not | Often yes |
| Reusability | High: work on any screen | Low: specific to one case |
| Ease of testing | Very high: input → output | Lower: you need to simulate data and logic |
| In CicloUrbano | BikeCard, StatusBadge, Footer |
BikeList, App |
The usefulness of this distinction is practical, not dogmatic: push logic upward and keep the leaves of the tree dumb. A BikeCard that doesn't know where its data comes from can be dropped into any screen, tested in two lines, and redesigned without fear.
An honest nuance: in the React community this separation used to be applied very strictly (people talked about "dumb" and "smart" components). With hooks, the line has become blurrier, and today it's considered a guideline, not a law. Even so, asking yourself "does this paint, or does this decide?" remains one of the best ways to clarify a confusing component.
- File organization and naming conventions
With five components, any organization works. With fifty, it doesn't. These are the conventions we'll use throughout the course.
src/ ├── components/ │ ├── Welcome.jsx │ ├── Header.jsx │ ├── StatusBadge.jsx │ ├── BikeList.jsx │ ├── Footer.jsx │ ├── BikeCard.jsx │ └── StationCard.jsx ├── data/ │ └── domain.js ├── App.jsx ├── index.css └── main.jsx
The rules, and the reason for each one:
| Rule | Example | Why |
|---|---|---|
| Component name in PascalCase | BikeCard |
JSX tells components apart from HTML tags by the initial capital letter (lesson 01-03) |
| One component per file, with the same name | BikeCard.jsx |
Finding the code behind what you see on screen is immediate |
.jsx extension if it contains JSX |
Header.jsx |
Makes it clear at a glance which files produce interface; domain.js is .js because it only holds data |
| Name by role, not by appearance | StatusBadge, not GreenSpan |
Appearance changes; the role a component plays, much less so |
| Names in English, like the rest of the domain | BikeList |
Consistent with the project; React's own APIs stay in English too |
On folder structure there are two schools: by type (components/, data/, hooks/) and by feature (catalogue/, bookings/, stations/). For a small application like ours, the first is simpler, and it's the one we'll follow. In Module 11, once CicloUrbano has several sections, we'll revisit whether it's worth migrating to the second.
- Default export vs named export
Every component file has to export something so other files can import it. There are two ways, and it's worth understanding the difference clearly.
Default export
// src/components/StatusBadge.jsx
function StatusBadge() {
return <span className="status status--disponible">disponible</span>;
}
export default StatusBadge;// When you import it, you choose the name (though you shouldn't change it)
import StatusBadge from './components/StatusBadge.jsx';There can be at most one default export per file. It's the dominant convention in React for "the main component of this file."
Named export
// When you import them, the names must match exactly and go inside braces
import { bikes, stations } from '../data/domain.js';There can be as many as you like per file. It's the natural form when a module exposes several things, like our domain.js.
Comparison
| Default | Named | |
|---|---|---|
| Export syntax | export default X; |
export const X = …; |
| Import syntax | import X from '…' |
import { X } from '…' |
| How many per file | One | Several |
| Can you rename it on import? | Yes, freely | Yes, with as: import { X as Y } |
| Editor autocomplete | Worse: the editor doesn't know the name | Better: the name is exact |
| Typo detection | Poor: import Card from './BikeCard.jsx' compiles |
Good: a nonexistent name throws an error |
Course convention: default export for components (one component per file) and named export for data and helper functions. It's the most widespread mix in Vite projects, and what you'll see in most real-world code.
- Composing: assembling the full screen
Let's write the new components and assemble the screen. Remember: the data is still hand-written inside each component, just like in lesson 01-03.
StatusBadge
// src/components/StatusBadge.jsx
function StatusBadge() {
return <span className="status status--disponible">disponible</span>;
}
export default StatusBadge;Two lines, and yet it's a legitimate component: it concentrates the decision of how a status gets represented. Once it receives the status from outside in the lesson Props, the status--disponible class will start being computed, and this component will be the only place in the project where that rule exists.
Header
// src/components/Header.jsx
import Welcome from './Welcome.jsx';
function Header() {
return (
<header className="header">
<Welcome />
<nav className="header__nav">
<ul>
<li>Catalogue</li>
<li>Stations</li>
<li>My bookings</li>
</ul>
</nav>
</header>
);
}
export default Header;Something new happens here: a component that uses another component. Header imports Welcome exactly the way App used to import it. There's no special hierarchy: any component can use any other component it imports.
Since Welcome now lives inside a <header>, its own one needs to be removed so as not to nest two headers, which would be semantically incorrect:
// src/components/Welcome.jsx (adjusted)
function Welcome() {
return (
<div className="welcome">
<h1>CicloUrbano</h1>
<p>Rent an urban bike in your district, by the hour, hassle-free.</p>
</div>
);
}
export default Welcome;This adjustment illustrates something you'll see constantly: component boundaries get renegotiated. Welcome started out as the entire header because there was nothing else; now that navigation has appeared, its role shrinks down to the welcome message, and Header takes over the frame. Refactoring boundaries is normal work, not a sign you got it wrong the first time.
BikeList
// src/components/BikeList.jsx
import BikeCard from './BikeCard.jsx';
function BikeList() {
return (
<section className="bike-list">
<h2>Available bikes</h2>
<BikeCard />
<BikeCard />
<BikeCard />
</section>
);
}
export default BikeList;This component is a container in the making: today it only groups things, but it's the box already prepared to receive the filters from Module 3.
BikeCard, now with StatusBadge inside
// src/components/BikeCard.jsx
import StatusBadge from './StatusBadge.jsx';
function BikeCard() {
return (
<article className="bike-card bike-card--urbana">
<h3>
Classic Urban <StatusBadge />
</h3>
<p>Type: urbana</p>
<p>Station: Main Square</p>
<p>
<strong>€2.50 / hour</strong>
</p>
</article>
);
}
export default BikeCard;Notice the bike-card--urbana class: the type variant we established in CicloUrbano's design. In the lesson Styling Components it will stop being hand-written and will start being built from the type.
App: assembling it all
// src/App.jsx
import Header from './components/Header.jsx';
import BikeList from './components/BikeList.jsx';
import Footer from './components/Footer.jsx';
function App() {
return (
<>
<Header />
<main>
<BikeList />
</main>
<Footer />
</>
);
}
export default App;Look at what's happened to App: it's down to three lines of JSX that read like a table of contents for the screen. That's the goal. A root component that reads like an outline is the best sign that the decomposition is well done; a two-hundred-line App.jsx is the opposite sign.
Add the styles for the new pieces to src/index.css:
/* Add to the end of src/index.css */
.header {
border-bottom: 1px solid #d9e2ec;
margin-bottom: 1.5rem;
}
.header__nav ul {
display: flex;
gap: 1rem;
list-style: none;
padding: 0;
margin: 0 0 1rem;
color: #12805c;
font-weight: 600;
}
.bike-list {
margin-bottom: 2rem;
}
.status {
display: inline-block;
padding: 0.15rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
vertical-align: middle;
color: #ffffff;
}
.status--disponible { background-color: #12805c; }
.status--alquilada { background-color: #b45309; }
.status--mantenimiento { background-color: #9b1c1c; }
.bike-card--urbana { border-left: 4px solid #12805c; }
.bike-card--electrica { border-left: 4px solid #b45309; }
.bike-card--carga { border-left: 4px solid #9b1c1c; }
- The limitation we're still carrying: the data is still fixed
Open the browser and you'll see the mockup's screen… with three identical bikes, all three "Classic Urban, disponible, Main Square." The structure is correct; the data isn't.
The cause is the same as in lesson 01-03: each component carries its data written inside it. And that's exactly the opposite of what we want, because it breaks the reusability criterion: a component whose data lives inside it can only ever show that data.
flowchart LR
A["Today: data INSIDE the component<br/>3 instances = 3 identical cards"] --> B["With props: data FROM OUTSIDE<br/>3 instances = 3 different bikes"]
The missing piece is props, and that's the content of the lesson Props: Passing Data to Components. There, BikeCard will receive a bike from domain.js, and the three cards will finally show bici-001, bici-002, and bici-003. Rendering the full array without writing out each card by hand comes a bit later, in Lists and Keys.
Common Mistakes and Tips
- Creating components "just in case." Extracting
SectionTitle,GrayParagraph, orFlexContainerbefore you have a real problem only adds indirection. Extract when the component has an obvious domain name, or when the second use shows up. - Letting
App.jsxgrow unchecked. It's the application's natural dumping ground. IfAppstarts containing detailed markup instead of a list of sections, extract that markup into a component. - Defining a component inside another one. Writing
function BikeCard() {…}inside the body ofBikeListcompiles, but a new function gets created on every render; for the reconciliation you studied in 01-05, that's a different type, so it destroys and recreates the entire subtree and loses its state. Always define components at the top level of the module. - Naming by appearance instead of by role.
GreenBoxstops making sense the day the design changes to blue.StatusBadgestays correct no matter what happens to the palette. - Confusing the component tree with the DOM tree.
Headerdoesn't generate a<Header>tag: it generates whatever's in itsreturn— which happens to include a real<header>element, but doesn't have to. A component can produce many nodes, one, or none. - Tip: sketch the tree before writing code. Five minutes with a sketch and a marker save an afternoon of reorganizing. And compare it afterward with the Components tab in React DevTools.
- Tip: apply the name test. If you can't give a component a short, descriptive name, the problem isn't the name: it's its boundaries.
Exercises
Exercise 1
CicloUrbano's application needs a station detail screen. Here's the sketch:
Main Square · Downtown district 20 docks · 12 free ──────────────────────────────── Bikes at this station [ bike card ] [ bike card ]
Identify the components you'd use, indicate which ones already exist and which would need to be created, and classify each one as presentation or container. Justify each decision with one of the three criteria from section 3.
Exercise 2
A colleague wrote this component. Spot the design problems and propose a better decomposition, without writing the full code: the resulting tree plus the name and responsibility of each piece is enough.
// src/components/Everything.jsx
function Everything() {
return (
<div>
<h1>CicloUrbano</h1>
<p>Rent an urban bike in your district…</p>
<div><span>Catalogue</span> <span>Stations</span></div>
<h2>Available bikes</h2>
<div className="bike-card">
<h3>Classic Urban <span className="status status--disponible">disponible</span></h3>
<p>€2.50 / hour</p>
</div>
<div className="bike-card">
<h3>Electric Pro <span className="status status--alquilada">alquilada</span></h3>
<p>€4.00 / hour</p>
</div>
<footer><p>CicloUrbano — Urban bike rental service</p></footer>
</div>
);
}Exercise 3
Create the FleetSummary component in src/components/FleetSummary.jsx. It should show, inside a <section className="fleet-summary">, an <h2> with the text "Fleet summary" and three lines with CicloUrbano's fleet totals according to domain.js: total bikes, available, and unavailable. Write the numbers by hand (we don't yet know how to compute them from the data) and place it in App.jsx between the header and the list. Then draw the updated component tree.
Solutions
Solution 1.
| Component | Does it exist? | Type | Justifying criterion |
|---|---|---|---|
StationDetail |
New | Container | Single responsibility: it coordinates the screen; it's the box where the station's logic will live |
StationCard |
Already exists (01-03) | Presentation | Reuse: the header with name, district, and docks is the same as in the station listing |
StationAvailability |
New | Presentation | Single responsibility: "20 docks · 12 free" is a piece of information with its own rules that will also appear on the map |
BikeList |
Already exists | Container | Reuse: exactly the same block from the catalogue, filtered by station |
BikeCard |
Already exists | Presentation | Reuse: it's the same component with no changes |
StatusBadge |
Already exists | Presentation | Reuse: third use of the badge |
What matters about this exercise is that four of the six components already existed. That's what you gain with well-drawn boundaries: the second screen costs far less than the first.
Solution 2.
Design problems:
- No single responsibility.
Everythingacts as header, navigation, catalogue, card, and footer all at once; its description would need four "and"s. - Zero reuse. The card's markup is literally duplicated, and so is the status badge's. Any design change has to be applied twice.
- Useless name.
Everythingdoesn't describe any responsibility, which is the classic symptom of badly drawn boundaries. - Non-semantic markup.
<div>for the cards and for the navigation, instead of<article>and<nav>.
Proposed decomposition:
flowchart TD
APP[App] --> CAB[Header]
APP --> LIS[BikeList]
APP --> PIE[Footer]
CAB --> BIE[Welcome]
LIS --> T1[BikeCard]
LIS --> T2[BikeCard]
T1 --> E1[StatusBadge]
T2 --> E2[StatusBadge]
| Component | Responsibility |
|---|---|
App |
Assembling the screen's three zones |
Header |
Site identity and navigation |
Welcome |
Title and tagline |
BikeList |
Section title and set of cards |
BikeCard |
Data for one bike |
StatusBadge |
Visual status badge |
Footer |
Closing information |
Solution 3.
// src/components/FleetSummary.jsx
function FleetSummary() {
return (
<section className="fleet-summary">
<h2>Fleet summary</h2>
<p>Total bikes: 5</p>
<p>Available: 3</p>
<p>Unavailable: 2</p>
</section>
);
}
export default FleetSummary;// src/App.jsx
import Header from './components/Header.jsx';
import FleetSummary from './components/FleetSummary.jsx';
import BikeList from './components/BikeList.jsx';
import Footer from './components/Footer.jsx';
function App() {
return (
<>
<Header />
<main>
<FleetSummary />
<BikeList />
</main>
<Footer />
</>
);
}
export default App;Updated tree:
flowchart TD
APP[App] --> CAB[Header]
APP --> RES[FleetSummary]
APP --> LIS[BikeList]
APP --> PIE[Footer]
CAB --> BIE[Welcome]
LIS --> T1[BikeCard]
LIS --> T2[BikeCard]
LIS --> T3[BikeCard]
T1 --> E1[StatusBadge]
T2 --> E2[StatusBadge]
T3 --> E3[StatusBadge]
The numbers "5, 3, 2" come from counting domain.js by hand, and that's exactly the problem: if a new bike gets added tomorrow, the summary will lie. Deriving those totals from the data is the subject of an upcoming lesson.
Conclusion
A component is React's unit of design: it encapsulates markup, logic, and style for a piece of interface, and its value depends above all on where you draw its boundaries. Now you have a method for drawing them: start from the sketch, circle the zones that have a name of their own, and validate each candidate against three criteria — single responsibility, reuse, and manageable size — avoiding both the component that does everything and fragmentation into trivial pieces.
You've turned CicloUrbano's catalogue sketch into a real component tree: App assembles Header, BikeList, and Footer; Header contains Welcome; BikeList contains BikeCard, and that in turn contains StatusBadge. You know how to tell presentation components apart from container components and how to use that distinction to push logic upward and keep the leaves reusable. And you've settled the conventions the whole course will follow: one component per file in src/components/, PascalCase, .jsx extension, default export for components and named export for data.
You've also hit the ceiling we're still bumping against: the structure is correct, but the three cards are identical, because the data is still written inside the components.
Before breaking through that ceiling, there's a mandatory stop. Everything you've written so far consists of function components, but in any React project with a few years behind it you'll run into components written with classes: a different syntax, with this, render(), and constructor. You won't be writing new code that way, but you do need to know how to read it. That's the subject of the next lesson: Function vs Class Components.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
