Overview
Functional components and JSX composition are the foundation of modern React applications. A functional component is a JavaScript or TypeScript function that returns React elements, usually written with JSX. JSX is a syntax extension that lets developers describe UI with HTML-like markup inside JavaScript.
function WelcomeCard({ name }: { name: string }) {
return (
<section className="card">
<h2>Hello, {name}</h2>
<p>Welcome back.</p>
</section>
);
}
Composition means building larger interfaces by combining smaller components. Instead of creating one large component that knows everything, React encourages breaking UI into focused pieces and nesting them together.
function Dashboard() {
return (
<PageLayout>
<Header />
<Sidebar />
<MainContent />
</PageLayout>
);
}
This topic matters in interviews because it tests whether a candidate understands React's mental model: UI is a tree of components, components should be pure during rendering, JSX is JavaScript under the hood, and reusable interfaces are built through composition rather than inheritance or direct DOM manipulation.
Strong React developers should be able to explain component naming rules, JSX syntax rules, fragments, children, conditional rendering, list rendering, keys, component extraction, render purity, and when composition is better than configuration-heavy component APIs.
Core Concepts
Functional Components
A functional component is a function that returns React elements.
function ProfileAvatar() {
return (
<img
src="/images/avatar.png"
alt="User avatar"
/>
);
}
Components can be reused and nested:
function UserProfile() {
return (
<article>
<ProfileAvatar />
<h2>Ava Nguyen</h2>
</article>
);
}
React component names must start with a capital letter. Lowercase JSX tags are treated as built-in HTML elements.
function button() {
return <button>Save</button>;
}
function App() {
return <button />; // HTML button, not the function above.
}
Correct:
function Button() {
return <button>Save</button>;
}
function App() {
return <Button />;
}
JSX
JSX lets developers write markup-like syntax inside JavaScript.
function ProductTitle({ name }: { name: string }) {
return <h1>{name}</h1>;
}
JSX is not a string and not HTML. It is syntax that tools transform into JavaScript calls that describe React elements.
Important JSX rules:
- A component must return one parent element.
- Use
classNameinstead ofclass. - Use
htmlForinstead offor. - Close all tags, including self-closing tags.
- Use camelCase for many DOM attributes and event handlers.
- Use curly braces for JavaScript expressions.
Example:
function SearchInput({ id }: { id: string }) {
return (
<label htmlFor={id}>
Search
<input id={id} className="search-input" />
</label>
);
}
Returning One Parent Element
JSX expressions must return a single parent element. This is invalid:
function UserHeader() {
return (
<h1>Ava</h1>
<p>Frontend Engineer</p>
);
}
Wrap the elements in a parent:
function UserHeader() {
return (
<header>
<h1>Ava</h1>
<p>Frontend Engineer</p>
</header>
);
}
Or use a fragment when no extra DOM element is needed:
function UserHeader() {
return (
<>
<h1>Ava</h1>
<p>Frontend Engineer</p>
</>
);
}
Fragments are useful when layout or semantics would be harmed by an unnecessary wrapper element.
JavaScript Expressions in JSX
Curly braces let you embed JavaScript expressions in JSX.
function MessageCount({ count }: { count: number }) {
return <p>You have {count} unread messages.</p>;
}
You can use expressions, not statements:
function Price({ value }: { value: number }) {
return <span>${value.toFixed(2)}</span>;
}
Invalid:
function Status({ isOnline }: { isOnline: boolean }) {
return <p>{if (isOnline) "Online"}</p>;
}
Use a ternary or compute before returning:
function Status({ isOnline }: { isOnline: boolean }) {
const label = isOnline ? "Online" : "Offline";
return <p>{label}</p>;
}
Objects cannot be rendered directly as children:
const user = { name: "Ava" };
return <p>{user}</p>; // Error.
Render a property or transform it:
return <p>{user.name}</p>;
JSX Attributes and Props
JSX attributes become props passed to components or DOM attributes for built-in elements.
function Button({ label }: { label: string }) {
return <button>{label}</button>;
}
function Toolbar() {
return <Button label="Save" />;
}
For dynamic values, use curly braces:
const isDisabled = formStatus === "submitting";
return <button disabled={isDisabled}>Submit</button>;
Boolean props can be passed with shorthand:
<button disabled>Submit</button>
This is equivalent to:
<button disabled={true}>Submit</button>
Component Composition
Composition means using components inside other components.
function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="card">
<h2>{title}</h2>
<div>{children}</div>
</section>
);
}
function Dashboard() {
return (
<Card title="Activity">
<p>No recent activity.</p>
</Card>
);
}
This pattern keeps the Card responsible for structure and styling while allowing the caller to provide content.
Composition is useful for:
- Layout components.
- Reusable form controls.
- Dialogs and modals.
- Cards and panels.
- Page shells.
- Feature-specific component trees.
The children Prop
children is a special prop containing whatever is placed between a component's opening and closing tags.
function Alert({ children }: { children: React.ReactNode }) {
return <div role="alert">{children}</div>;
}
function SaveError() {
return (
<Alert>
<strong>Save failed.</strong> Please try again.
</Alert>
);
}
children supports flexible composition because the parent does not need to predict every possible piece of content.
Do not overuse children when named props communicate intent better:
function UserCard({
avatar,
title,
actions,
}: {
avatar: React.ReactNode;
title: React.ReactNode;
actions: React.ReactNode;
}) {
return (
<article>
<div>{avatar}</div>
<h2>{title}</h2>
<footer>{actions}</footer>
</article>
);
}
This is still composition, but with named slots.
Conditional Rendering
Components can return different JSX based on data.
function LoginButton({ isLoggedIn }: { isLoggedIn: boolean }) {
if (isLoggedIn) {
return <button>Log out</button>;
}
return <button>Log in</button>;
}
Ternary expressions are useful for small conditions:
function StatusBadge({ online }: { online: boolean }) {
return <span>{online ? "Online" : "Offline"}</span>;
}
Logical && is useful for optional rendering:
function ErrorMessage({ message }: { message?: string }) {
return (
<>
{message && <p role="alert">{message}</p>}
</>
);
}
Be careful with numeric values:
{count && <Badge count={count} />}
If count is 0, React may render 0 instead of rendering nothing. Prefer an explicit condition:
{count > 0 && <Badge count={count} />}
Rendering Lists
Use array methods such as map to render lists.
type User = {
id: string;
name: string;
};
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Every item in a list needs a stable key. The key helps React match items between renders.
Good key:
<li key={user.id}>{user.name}</li>
Risky key:
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
Index keys are risky when items can be inserted, removed, reordered, filtered, or sorted. They can cause incorrect state preservation and confusing UI bugs.
Component Extraction
Extract a component when a piece of UI has a clear responsibility, is repeated, or makes a parent component hard to read.
Before:
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button>Add to cart</button>
</li>
))}
</ul>
);
}
After:
function ProductCard({ product }: { product: Product }) {
return (
<li>
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button>Add to cart</button>
</li>
);
}
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</ul>
);
}
Do not extract components mechanically. Too many tiny components can make the code harder to follow. Extract around meaningful responsibilities.
Purity During Rendering
React components should be pure during rendering: given the same inputs, they should return the same JSX and should not mutate values that existed before rendering.
Bad:
let nextId = 0;
function Item() {
nextId += 1;
return <li>Item {nextId}</li>;
}
This changes external state during render. It can break when React re-renders, retries, or runs extra development checks.
Better:
function Item({ id }: { id: number }) {
return <li>Item {id}</li>;
}
Side effects belong in event handlers or effects, not in render logic.
function SaveButton() {
function handleClick() {
analytics.track("save_clicked");
}
return <button onClick={handleClick}>Save</button>;
}
Component Definitions Should Stay at Top Level
Avoid defining components inside other components.
function Parent() {
function Child() {
return <p>Child</p>;
}
return <Child />;
}
This creates a new component function every render and can cause state to reset unexpectedly.
Prefer top-level definitions:
function Child() {
return <p>Child</p>;
}
function Parent() {
return <Child />;
}
If the child needs data, pass it with props:
function Child({ label }: { label: string }) {
return <p>{label}</p>;
}
function Parent() {
return <Child label="Child" />;
}
Composition vs Inheritance
React code usually favors composition over inheritance. Instead of creating a base class or a highly abstract component hierarchy, you combine components and pass data, event handlers, or JSX.
function Dialog({
title,
children,
actions,
}: {
title: string;
children: React.ReactNode;
actions: React.ReactNode;
}) {
return (
<section role="dialog" aria-label={title}>
<h2>{title}</h2>
<div>{children}</div>
<footer>{actions}</footer>
</section>
);
}
Usage:
<Dialog
title="Delete project"
actions={<button>Confirm</button>}
>
<p>This action cannot be undone.</p>
</Dialog>
Composition keeps behavior explicit at the usage site and avoids deep inheritance chains.
Common Mistakes
Common mistakes include:
- Naming components with lowercase names.
- Returning multiple sibling JSX elements without a wrapper or fragment.
- Forgetting to close tags.
- Using
classinstead ofclassName. - Using statements instead of expressions inside JSX braces.
- Rendering objects directly.
- Defining components inside components.
- Mutating external values during render.
- Using array index keys for reorderable lists.
- Extracting components before there is a clear responsibility.
- Creating component APIs with too many boolean flags instead of composing smaller pieces.
Best Practices
Use these rules of thumb:
- Keep components pure during rendering.
- Name components with PascalCase.
- Use JSX to describe UI from data.
- Break components around clear responsibilities.
- Prefer composition through
children, named slots, and smaller components. - Use fragments to avoid unnecessary DOM wrappers.
- Use stable keys for list items.
- Keep side effects in event handlers or effects.
- Let parent components coordinate structure while child components handle focused display logic.