React course menu
Component Composition in React: children and Slots
Build flexible React components with composition: the children prop, layout components, passing components and elements as props (slots), specialisation instead of configuration, render props, and avoiding prop drilling with composition.
On this page
React components are combined by composition: putting components inside other components and passing pieces of UI around as props. React does not use class inheritance for sharing UI; composition covers every case more simply.
children: components as containers#
A component that renders {children} can wrap anything:
function Modal({ title, children }) {
return (
<div style={{ border: "1px solid #ccc", borderRadius: 10, maxWidth: 320, boxShadow: "0 4px 14px rgba(0,0,0,.12)" }}>
<div style={{ padding: "8px 12px", borderBottom: "1px solid #eee", fontWeight: "bold" }}>{title}</div>
<div style={{ padding: 12 }}>{children}</div>
</div>
);
}
export default function App() {
return (
<Modal title="Delete file?">
<p style={{ marginTop: 0 }}>“report.pdf” will be permanently deleted.</p>
<button>Cancel</button> <button style={{ color: "white", background: "crimson", border: 0, padding: "4px 10px" }}>Delete</button>
</Modal>
);
}Modal knows nothing about its contents: the same component works for confirmations, forms or images.
Several slots#
When a component has several places to fill, pass each as its own prop. JSX can be passed like any other value:
function PageLayout({ header, sidebar, children }) {
return (
<div style={{ display: "grid", gridTemplateColumns: "110px 1fr", gap: 8, fontSize: 14 }}>
<div style={{ gridColumn: "1 / -1", background: "#e7f5ff", padding: 6 }}>{header}</div>
<aside style={{ background: "#f8f9fa", padding: 6 }}>{sidebar}</aside>
<main style={{ padding: 6 }}>{children}</main>
</div>
);
}
export default function App() {
return (
<PageLayout
header={<strong>Laviyx Learn</strong>}
sidebar={<ul style={{ margin: 0, paddingLeft: 16 }}><li>React</li><li>Python</li></ul>}
>
<h3 style={{ marginTop: 0 }}>Composition</h3>
<p>The layout decides where things go; the page decides what they are.</p>
</PageLayout>
);
}Specialisation instead of configuration#
A generic component plus small specialised ones is clearer than one component with many flags:
function Alert({ tone, icon, children }) {
const colors = { info: ["#e7f5ff", "#1c7ed6"], success: ["#ebfbee", "#2f9e44"], danger: ["#fff5f5", "#e03131"] };
const [bg, fg] = colors[tone];
return (
<p style={{ background: bg, color: fg, padding: "8px 12px", borderRadius: 6, margin: "6px 0" }}>
{icon} {children}
</p>
);
}
const SuccessAlert = ({ children }) => <Alert tone="success" icon="✔">{children}</Alert>;
const ErrorAlert = ({ children }) => <Alert tone="danger" icon="✖">{children}</Alert>;
export default function App() {
return (
<div>
<Alert tone="info" icon="ℹ">Your plan renews on 1 November.</Alert>
<SuccessAlert>Payment received.</SuccessAlert>
<ErrorAlert>Card declined. Try another card.</ErrorAlert>
</div>
);
}Composition reduces prop drilling#
If a value only travels through middle components to reach a deep child, you can often build the deep child at the top and pass it down as children, so the middle components never touch the value:
// Before: Page and Sidebar only pass `user` along
<Page user={user} /> // → <Sidebar user={user} /> → <Avatar user={user} />
// After: build Avatar where `user` lives
<Page>
<Sidebar>
<Avatar user={user} />
</Sidebar>
</Page>Try composition before reaching for context.
Render props#
Sometimes a component needs to give data back to the content it renders. It can take a function as a prop and call it with that data. This pattern is called a render prop:
import { useState } from "react";
function Hoverable({ children }) {
const [hovered, setHovered] = useState(false);
return (
<div onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ display: "inline-block" }}>
{children(hovered)}
</div>
);
}
export default function App() {
return (
<Hoverable>
{(hovered) => (
<button style={{ background: hovered ? "#4c6ef5" : "#edf2ff", color: hovered ? "white" : "#364fc7", border: 0, padding: "8px 14px" }}>
{hovered ? "Let's go!" : "Hover me"}
</button>
)}
</Hoverable>
);
}Today, custom hooks handle most of these cases more simply (const hovered = useHover(ref)), but you will meet render props in libraries.
Common mistakes#
Practice#
Practice exercise
Create a Panel component with title and actions slots plus children for the body, and use it to show a "Profile" panel with an "Edit" button in the actions slot.
Show hint
function Panel({ title, actions, children }) renders a header row with the title on the left and {actions} on the right, then the body.
Show solution
function Panel({ title, actions, children }) {
return (
<section style={{ border: "1px solid #ddd", borderRadius: 8, maxWidth: 340 }}>
<header style={{ display: "flex", justifyContent: "space-between", padding: 8, borderBottom: "1px solid #eee" }}>
<strong>{title}</strong>
<div>{actions}</div>
</header>
<div style={{ padding: 8 }}>{children}</div>
</section>
);
}
export default function App() {
return (
<Panel title="Profile" actions={<button>Edit</button>}>
<p style={{ margin: 0 }}>Name: Tara Iyer<br />City: Kochi</p>
</Panel>
);
}Key takeaway#
Next: Styling React Components.
Finished this lesson?