Components & Props
Components are the building blocks of every React app, and props are how data flows into them. Master these two ideas and you can compose any interface from small, testable, reusable pieces.
Function components
A component is a JavaScript function that returns JSX. Its name must start with a capital letter—that’s how React (and JSX) distinguishes your components from built-in DOM tags like div.
function Button() {
return <button className="btn">Click me</button>;
}
Render it by using it as a tag: <Button />. That’s all a component is—no special base class, no boilerplate.
Props are read-only inputs
Props are the arguments you pass when rendering a component. The component receives them as a single object, which you almost always destructure:
function Avatar({ src, alt, size = 48 }) {
return <img src={src} alt={alt} width={size} height={size} />;
}
// usage
<Avatar src="/ada.png" alt="Ada Lovelace" size={64} />
The single most important rule: a component must never mutate its props. Props flow down and are read-only from the child’s perspective. Treating them as immutable is what makes React’s data flow predictable. If a child needs to change something, the parent passes a callback prop:
function ToggleButton({ on, onToggle }) {
return <button onClick={onToggle}>{on ? "On" : "Off"}</button>;
}
Default props via destructuring
Modern React sets defaults right in the parameter destructuring—the old Component.defaultProps API is deprecated for function components.
function Alert({ variant = "info", dismissible = false, children }) {
return <div className={`alert alert-${variant}`}>{children}</div>;
}
Any prop the caller omits falls back to the default.
Composition and children
React favors composition over inheritance. Components wrap other components, and the special children prop holds whatever you nest inside a component’s tags.
function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</section>
);
}
// usage
<Card title="Welcome">
<p>Anything here becomes <code>children</code>.</p>
<Button />
</Card>
Output:
┌─ Welcome ──────────────────────────┐
│ Anything here becomes children. │
│ [ Click me ] │
└────────────────────────────────────┘
This pattern—generic “container” components that don’t know what they’ll contain—is how you build flexible design systems without rigid inheritance hierarchies.
Typing props with TypeScript
TypeScript turns props into a contract the compiler enforces. Define an interface (or type) and annotate the parameter:
interface AvatarProps {
src: string;
alt: string;
size?: number; // optional
onClick?: () => void; // callback prop
}
function Avatar({ src, alt, size = 48, onClick }: AvatarProps) {
return (
<img src={src} alt={alt} width={size} height={size} onClick={onClick} />
);
}
For components that accept nested content, type children with React.ReactNode:
interface CardProps {
title: string;
children: React.ReactNode;
}
Now callers get autocomplete, and passing the wrong prop type is a compile error rather than a runtime surprise.
Best Practices
- Keep components small and focused—one clear responsibility each.
- Treat props as immutable; never reassign or mutate them inside the component.
- Lift configuration into props so components stay reusable across contexts.
- Prefer composition with
childrenover deep prop drilling or inheritance. - Type every component’s props with TypeScript; mark optional props with
?and give sensible defaults. - Name components in PascalCase and keep one component per file for larger components.