Goal: split an interface into small function components with clear jobs, compose a page from them, and judge when a piece of UI deserves its own component and when it doesn’t.
The tool library’s catalogue page has a header, a search box, a grid of tool cards and a footer. Written as one function, it would grow to hundreds of lines in which a change to the footer sits next to the logic for reserving a drill. Components let you give each part a name, a boundary and a single job, and then build the page by putting the parts together. That act of putting together is composition, and it is how every React application, from a widget to a full Next.js site, is structured.
In this lesson
A component is a function
A React component is a JavaScript function that returns JSX. Once it exists, you use it like a new HTML tag:
function SiteHeader() {
return (
<header>
<strong>Neighbourhood Tool Library</strong>
<p>Borrow it, use it, bring it back.</p>
</header>
);
}
function App() {
return (
<>
<SiteHeader />
<SiteHeader />
</>
);
}
The header appears twice, because App uses it twice. Each <SiteHeader /> is a separate instance: React calls the function once for each place it appears.
The name must start with a capital letter. JSX uses the first letter to decide what a tag means: lowercase tags such as <header> are HTML elements, capitalised tags such as <SiteHeader /> are components. If you call your function toolCard and write <toolCard />, React never calls your function. It tries to create an HTML element called toolCard, which renders nothing useful, and the console shows a warning that the tag is unrecognised and should start with an uppercase letter.
Use a component as a tag, not by calling it yourself. {SiteHeader()} may seem to work for a simple component, but React then doesn’t see a component at all, only the JSX it returned. Once a component has state of its own (Lesson 5) or uses other hooks, calling it like this ties those hooks to the parent instead and breaks in confusing ways. <SiteHeader /> lets React manage each instance properly.
Everything from Lesson 1 applies to each component: it should be a pure function of its inputs. That is what makes composition safe. A SiteFooter that doesn’t change anything outside itself can be placed on any page, any number of times, without surprises.
Composing a page from components
Composition means a component’s JSX can include other components, which can include others, and so on. The result is a tree. Here is the shape of the catalogue page you will build in this lesson’s exercise:
CatalogPage
├── SiteHeader
├── main
│ ├── SearchBar
│ └── ToolGrid
│ ├── ToolCard (Cordless drill)
│ ├── ToolCard (Extension ladder)
│ └── ToolCard (…one per tool)
└── SiteFooter
Reading the top component should feel like reading a table of contents. CatalogPage says what is on the page; the details of each part live inside that part. A small version, with two parts filled in:
function SiteHeader() {
return (
<header>
<strong>Neighbourhood Tool Library</strong>
</header>
);
}
function SiteFooter() {
return (
<footer>
<p>Open Saturdays 10:00 to 13:00. Run by volunteers.</p>
</footer>
);
}
function CatalogPage() {
return (
<>
<SiteHeader />
<main>
<h1>Tool catalogue</h1>
<p>Eight tools to borrow, free for members.</p>
</main>
<SiteFooter />
</>
);
}
Components and plain HTML mix freely: CatalogPage renders the <main> landmark itself, because the page is the right place to decide where the main content starts. A page should have exactly one <main>; screen-reader users jump straight to it, so keep the header and footer outside it. Notice also that the components don’t produce extra wrappers: SiteHeader returns a <header>, nothing more. The final page contains only the elements you wrote.
Components in files: import and export
The live editor runs one file at a time, so this course’s runnable examples keep every component together. In a real project you would usually give each sizeable component its own file and connect them with JavaScript modules:
src/
├── components/
│ ├── SiteHeader.jsx
│ ├── SiteFooter.jsx
│ ├── SearchBar.jsx
│ ├── ToolGrid.jsx
│ └── ToolCard.jsx
└── pages/
└── CatalogPage.jsx
// components/SiteFooter.jsx
export function SiteFooter() {
return (
<footer>
<p>Open Saturdays 10:00 to 13:00. Run by volunteers.</p>
</footer>
);
}
// pages/CatalogPage.jsx
import { SiteHeader } from '../components/SiteHeader';
import { SiteFooter } from '../components/SiteFooter';
export function CatalogPage() {
// …
}
These are named exports: the importing file uses the exact name in braces. A file can instead have one default export (export default function SiteFooter()), imported without braces under any name you like (import SiteFooter from '…'). Both are correct React. I recommend named exports for your own components, because the name stays the same everywhere and your editor can rename it safely, and default exports where a framework requires them: Next.js expects each route’s page file to default-export its component (Lesson 20). The folder layout above is only a starting point; Lesson 44 discusses structuring a larger code base.
Small helper components that only one file uses can stay in that file, unexported. Not every component needs its own file; it needs a file when other files use it or when it grows big enough that it is easier to find on its own.
When to split a component, and when not to
Splitting has a cost: each component is another name to learn and another place to look. It pays off when the new component has a clear job. Good reasons to extract one:
- You can describe its job in one short sentence without “and”: “shows one tool”, “lets members search the catalogue”. If the sentence needs an “and”, it may be two components.
- It repeats.
ToolCardappears once per tool, so it is a component almost by definition. - It changes for its own reasons. The footer changes when opening hours change; the grid changes when the catalogue layout does. Separate components keep those edits apart, which also keeps code reviews small.
- The parent has become hard to read. When you scroll to understand a component’s JSX, naming its sections as components restores the table-of-contents view.
- It will own some state. Lesson 13 shows that keeping state in a small component limits what re-renders when it changes.
Equally important is knowing when not to split. Don’t wrap single HTML elements (a Paragraph component that only returns <p> adds a name and gives nothing back). And be wary of merging two things just because they look alike today. The catalogue’s ToolCard and the basket’s line for a reserved tool both show a tool name and a badge, so it is tempting to build one Card for both. But the basket line will get dates and a Remove button, and the catalogue card will get a photo and availability. A shared component soon fills up with switches such as showDates, showPhoto and isBasket, and each change for one screen risks breaking the other. Some duplication is cheaper than the wrong abstraction. Share the genuinely identical part instead, such as the status badge, which Lesson 4 turns into a StatusBadge component.
A practical rule: write it inline first, extract when one of the reasons above becomes true, and prefer several small, specific components over one clever, configurable one.
Define components at the top level
Components can use each other in any arrangement, but they should always be defined at the top level of a file, never inside another component. This example breaks that rule. It uses state from Lesson 1: the page counts tools added to the basket, and a small HelpfulButton keeps its own count. Run it, click Helpful a few times, then click Add a tool:
import { useState } from 'react';
function CatalogPage() {
const [basketCount, setBasketCount] = useState(0);
// Wrong: a new HelpfulButton component is created on every render.
function HelpfulButton() {
const [votes, setVotes] = useState(0);
return <button onClick={() => setVotes(votes + 1)}>Helpful ({votes})</button>;
}
return (
<div>
<p>Tools in basket: {basketCount}</p>
<button onClick={() => setBasketCount(basketCount + 1)}>Add a tool</button>
<p>Was the catalogue useful? <HelpfulButton /></p>
</div>
);
}
function App() {
return <CatalogPage />;
}
The basket count goes up, and the Helpful count drops back to 0. Each time CatalogPage renders, it creates a brand-new HelpfulButton function. To React, a different function is a different component, so it removes the old button, with its state, and creates a fresh one. With an input field instead of a button, the symptom is a text box that loses focus and what you typed after every keystroke. Cut HelpfulButton out, paste it above CatalogPage and run again: now its count survives. Lesson 4 shows how a top-level component gets the data it needs through props.
Common mistakes
- Lowercase component names.
<toolGrid />renders an unknown HTML element instead of your component. The console warning about an unrecognised tag or incorrect casing is the giveaway. - The giant page component. One function holding the header, search, grid, footer and every handler. In review, if the JSX doesn’t fit on a screen or covers unrelated jobs, suggest named sections.
- Premature abstraction. A shared component with many boolean props that each switch off part of it for one caller. Look for props like
isBasketorhideX: they usually mean two components were forced into one. - Components defined inside components. State resets and inputs lose focus on every parent render. Search for
functiondeclarations with capitalised names nested inside another component’s body.
Build it: the catalogue page
The starter is a working catalogue page written as one component. Split it into SiteHeader, SearchBar, ToolGrid, ToolCard and SiteFooter, so that CatalogPage reads like the tree earlier in this lesson. The page must look exactly the same afterwards. The search box is static for now; Lesson 7 makes it filter. Then decide which of the five you would give their own file in a real project, and why.
const TOOLS = [
{ id: 't1', name: 'Cordless drill', category: 'Power tools', status: 'available' },
{ id: 't2', name: 'Extension ladder', category: 'Ladders & access', status: 'on-loan' },
{ id: 't3', name: 'Orbital sander', category: 'Power tools', status: 'available' },
];
function CatalogPage() {
return (
<>
<header><strong>Neighbourhood Tool Library</strong></header>
<main>
<h1>Tool catalogue</h1>
<form role="search">
<label htmlFor="tool-search">Search tools</label>{' '}
<input id="tool-search" type="search" />
</form>
<section aria-label="Tools">
{TOOLS.map((tool) => (
<article key={tool.id}>
<h2>{tool.name}</h2>
<p>{tool.category}</p>
</article>
))}
</section>
</main>
<footer><p>Open Saturdays 10:00 to 13:00.</p></footer>
</>
);
}
Hint: work from the outside in. Header, search and footer need no data, so extract them first. For the grid, ToolGrid keeps the map, and each item becomes <ToolCard key={tool.id} tool={tool} />, with ToolCard receiving { tool } as in Lesson 2’s exercise. The key stays on the outermost element of the map, which is now the ToolCard.
Show a sample solution
const TOOLS = [
{ id: 't1', name: 'Cordless drill', category: 'Power tools', status: 'available' },
{ id: 't2', name: 'Extension ladder', category: 'Ladders & access', status: 'on-loan' },
{ id: 't3', name: 'Orbital sander', category: 'Power tools', status: 'available' },
];
function SiteHeader() {
return <header><strong>Neighbourhood Tool Library</strong></header>;
}
function SearchBar() {
return (
<form role="search">
<label htmlFor="tool-search">Search tools</label>{' '}
<input id="tool-search" type="search" />
</form>
);
}
function ToolCard({ tool }) {
return (
<article>
<h2>{tool.name}</h2>
<p>{tool.category}</p>
</article>
);
}
function ToolGrid() {
return (
<section aria-label="Tools">
{TOOLS.map((tool) => <ToolCard key={tool.id} tool={tool} />)}
</section>
);
}
function SiteFooter() {
return <footer><p>Open Saturdays 10:00 to 13:00.</p></footer>;
}
function CatalogPage() {
return (
<>
<SiteHeader />
<main>
<h1>Tool catalogue</h1>
<SearchBar />
<ToolGrid />
</main>
<SiteFooter />
</>
);
}
The page renders the same text as before, and CatalogPage now fits in a dozen lines that name each part. The <h1> stayed in the page because it describes the page, not any one part. On files: SiteHeader and SiteFooter will be shared by every page, and ToolCard will also appear in search results and on a member’s page, so each earns its own file. SearchBar and ToolGrid are used only by the catalogue for now, so keeping them in the page’s file is reasonable; move them out once something else needs them or they grow. There is no single right answer; the reasoning is what matters.
One weakness remains: ToolGrid reads the global TOOLS array, so it can only ever show the full catalogue. Lesson 4 fixes that by passing the list in as a prop.
Check your understanding
Answer each question in a sentence or two before opening the answer.
1. You wrote function statusBadge() and used <statusBadge />, but no badge appears. Why, and where would you look first?
JSX treats lowercase tags as HTML elements, so React created an unknown statusBadge element and never called your function. The browser console shows a warning that the tag is unrecognised and that components should start with an uppercase letter. Rename it StatusBadge.
2. A teammate wants one ItemCard for both catalogue cards and basket lines, with props showDates, showPhoto and showRemove. What would you suggest?
Keep two components, ToolCard and a basket line component, because they change for different reasons and the flags would multiply as each screen grows. Extract only what is truly identical, such as the status badge, into a shared component.
3. Testers report that the search box loses focus after every letter they type. What is a likely cause, and how would you confirm it?
The search component is probably defined inside its parent component, so each parent render creates a new component type and React replaces the input. Look for a capitalised function declared inside another component’s body; moving it to the top level should fix it, and typing several letters in a row confirms that focus now stays.
Key takeaways
- A component is a function that returns JSX. Name it with a capital letter and use it as a tag, not by calling it.
- Composition builds pages as a tree of components; the top component should read like a table of contents.
- Split when a part has one clear job, repeats, changes for its own reasons or makes its parent hard to read. Don’t wrap single elements, and don’t merge things that only look alike.
- Define every component at the top level of a file; a component defined inside another loses its state on each parent render.
Priority: should know. You should be able to break a page into components and justify each boundary in a review. The React documentation covers the basics in Your First Component and Importing and Exporting Components.
