B3 · Part 1 · Beginner
Components & composition
A component is a function that takes data and returns a description of a screen.
12On this page
The whole idea
A React component is a JavaScript function that returns JSX. That is the
entire definition. It takes one argument, conventionally called
props, and it returns an element tree.
function Greeting() {
return <h1>Hello</h1>;
}
// used like a tag
<Greeting />
The capital letter is not style
<greeting /> compiles to jsx("greeting", ...)
— the string "greeting", which React reads as "an HTML tag
called greeting". <Greeting /> compiles to
jsx(Greeting, ...) — a reference to your function.
Lowercase means DOM tag; capitalised means component. That is why a lowercase component silently renders nothing instead of erroring.
Composition
Components nest, and that is how every React app is built: one tree, made of functions calling into other functions.
function App() {
return (
<Page>
<Header />
<Article title="Closures" />
<Footer />
</Page>
);
}
children
Whatever you put between a component's tags arrives as
props.children. This is the mechanism behind every wrapper you
will ever write — cards, modals, layouts, page shells.
function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
<div className="card__body">{children}</div>
</section>
);
}
<Card title="Notes">
<p>Anything at all goes here.</p>
<Button />
</Card>
Card knows nothing about what it wraps. That ignorance is the
point: it is what makes it reusable. A component that reaches inside its
children to check what they are has usually made a mistake.
Composition instead of configuration
When a component grows a pile of boolean props —
showHeader, showFooter, compact,
withIcon — that is usually a sign it should be taking
children instead. Passing the header in is more flexible than
passing a flag that asks it to draw one.
Named slots
children is one slot. When you need several, pass elements as
ordinary props — there is nothing special about children
except its name.
function Layout({ sidebar, main }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{main}</main>
</div>
);
}
<Layout sidebar={<Nav />} main={<Article />} />
Components must be pure
Given the same props, a component must return the same thing and change nothing outside itself. React relies on this: it may call your function more than once for a single screen, and in development it deliberately does, to surface exactly this kind of bug.
| Inside the component body | |
|---|---|
| fine | Reading props and state, computing values, building JSX |
| no | Mutating a prop or a variable defined outside |
| no | Writing to the DOM directly |
| no | Fetching, timers, subscriptions, localStorage |
Everything in the "no" column belongs in an event handler or an effect. Purity is not dogma here — it is the assumption that lets React render, abandon that render, and start again without your code noticing.
Splitting one that grew
function Dashboard({ userId }) {
// fetches, formats, and renders three unrelated panels
const [user, setUser] = useState(null);
const [invoices, setInvoices] = useState([]);
const [alerts, setAlerts] = useState([]);
...180 lines...
}
The split follows the data, not the line count:
function Dashboard({ userId }) {
return (
<Grid>
<ProfilePanel userId={userId} />
<InvoicePanel userId={userId} />
<AlertPanel userId={userId} />
</Grid>
);
}
Each panel now owns its own loading and error state, so a slow invoice request no longer blocks the profile from appearing. That is the real payoff of splitting — not tidiness, but independent failure and independent loading.
The mistake that costs an afternoon
Never define a component inside another component.
function Page() {
// ✗ a brand-new function on every render
function Row({ label }) {
return <li>{label}</li>;
}
return <ul><Row label="one" /></ul>;
}
Each render creates a new Row function. React compares element
types to decide whether to keep a DOM node, and a new function is a
new type — so it throws the old node away and builds a fresh one every
render. Anything inside loses its state, its focus and its scroll position.
Move Row out to the module level and it is fixed.
The render tree is not the DOM tree
React builds a tree of your components; the browser holds a tree of DOM nodes. They are different shapes, and knowing that saves you when debugging.
// render tree // DOM tree
App div.layout
└ Layout ├ aside
├ Nav │ └ ul...
└ Article └ main
└ Markdown └ article...
Layout, Nav and Article produce no DOM
node of their own — they are functions that returned something. This is
why React DevTools shows a tree the Elements panel does not, and why
"wrapping in a component" costs nothing in the output.
Returning nothing
function Banner({ message }) {
if (!message) return null; // legal, renders nothing
return <div className="banner">{message}</div>;
}
null, undefined and false are all valid
returns. A component that renders conditionally does not need a wrapper
around its call site — it can decide for itself.
One component per file, named after the file
Not a rule React enforces, but the convention every codebase converges on because it makes a file findable from a stack trace and from an import. Helpers used only by that component live in the same file, below it.
components/
TaskList.jsx // export function TaskList
TaskList.module.css
TaskRow.jsx
Give the function a name rather than exporting an anonymous arrow. React
DevTools and error stacks both read that name, and
export default () => ... shows up as Anonymous.
How big should a component be?
There is no line count that answers this. The useful test is whether it does one thing: if you cannot name it without saying "and", it is two components. A 200-line form component that is just a long form is fine. A 60-line component that fetches, formats and renders three unrelated panels is not.
Interview answer, one sentence
"A component is a pure function from props to elements. Composition — passing elements in as children or props — is how React avoids the configuration explosion you get when a component tries to know about every variation of itself."
Opens in the editor: write it, run it, and check it against real tests.
All 46 for this level →Finished reading?
0 of 57 chapters read in this series