Composition in React
React uses composition over inheritance. This means building complex UIs by combining simpler components.
Containment
Components can contain other components using children:
jsx
1function Card({ children, title }) {
2 return (
3 <div className="card">
4 <h2 className="card-title">{title}</h2>
5 <div className="card-content">
6 {children}
7 </div>
8 </div>
9 )
10}
11
12// Usage
13function App() {
14 return (
15 <Card title="Welcome">
16 <p>This is the card content.</p>
17 <button>Learn More</button>
18 </Card>
19 )
20}Multiple Slots
Use named props for multiple composition points:
jsx
1function Layout({ header, sidebar, content, footer }) {
2 return (
3 <div className="layout">
4 <header className="header">{header}</header>
5 <div className="main">
6 <aside className="sidebar">{sidebar}</aside>
7 <main className="content">{content}</main>
8 </div>
9 <footer className="footer">{footer}</footer>
10 </div>
11 )
12}
13
14// Usage
15function App() {
16 return (
17 <Layout
18 header={<Navigation />}
19 sidebar={<SideMenu />}
20 content={<MainContent />}
21 footer={<FooterInfo />}
22 />
23 )
24}Specialization
Create specialized versions of generic components:
jsx
1// Generic button
2function Button({ variant = 'default', size = 'medium', children, ...props }) {
3 return (
4 <button
5 className={`btn btn-${variant} btn-${size}`}
6 {...props}
7 >
8 {children}
9 </button>
10 )
11}
12
13// Specialized buttons
14function PrimaryButton(props) {
15 return <Button variant="primary" {...props} />
16}
17
18function DangerButton(props) {
19 return <Button variant="danger" {...props} />
20}
21
22function SmallButton(props) {
23 return <Button size="small" {...props} />
24}
25
26// Usage
27function App() {
28 return (
29 <div>
30 <PrimaryButton>Save</PrimaryButton>
31 <DangerButton>Delete</DangerButton>
32 <SmallButton variant="primary">Small Primary</SmallButton>
33 </div>
34 )
35}Dialog Component Pattern
jsx
1function Dialog({ title, children, actions, isOpen, onClose }) {
2 if (!isOpen) return null
3
4 return (
5 <div className="dialog-overlay" onClick={onClose}>
6 <div className="dialog" onClick={e => e.stopPropagation()}>
7 <div className="dialog-header">
8 <h2>{title}</h2>
9 <button onClick={onClose}>×</button>
10 </div>
11 <div className="dialog-body">
12 {children}
13 </div>
14 {actions && (
15 <div className="dialog-actions">
16 {actions}
17 </div>
18 )}
19 </div>
20 </div>
21 )
22}
23
24// Specialized dialogs
25function ConfirmDialog({ message, onConfirm, onCancel, isOpen }) {
26 return (
27 <Dialog
28 title="Confirm"
29 isOpen={isOpen}
30 onClose={onCancel}
31 actions={
32 <>
33 <button onClick={onCancel}>Cancel</button>
34 <button onClick={onConfirm}>Confirm</button>
35 </>
36 }
37 >
38 <p>{message}</p>
39 </Dialog>
40 )
41}
42
43function AlertDialog({ message, onClose, isOpen }) {
44 return (
45 <Dialog
46 title="Alert"
47 isOpen={isOpen}
48 onClose={onClose}
49 actions={<button onClick={onClose}>OK</button>}
50 >
51 <p>{message}</p>
52 </Dialog>
53 )
54}Render Props Pattern
Pass a function as children for flexible rendering:
jsx
1function Mouse({ children }) {
2 const [position, setPosition] = useState({ x: 0, y: 0 })
3
4 useEffect(() => {
5 const handleMouseMove = (e) => {
6 setPosition({ x: e.clientX, y: e.clientY })
7 }
8
9 window.addEventListener('mousemove', handleMouseMove)
10 return () => window.removeEventListener('mousemove', handleMouseMove)
11 }, [])
12
13 return children(position)
14}
15
16// Usage - complete control over rendering
17function App() {
18 return (
19 <Mouse>
20 {({ x, y }) => (
21 <div>
22 Mouse position: {x}, {y}
23 </div>
24 )}
25 </Mouse>
26 )
27}Compound Components
Components that work together:
jsx
1const TabsContext = createContext()
2
3function Tabs({ children, defaultTab }) {
4 const [activeTab, setActiveTab] = useState(defaultTab)
5
6 return (
7 <TabsContext.Provider value={{ activeTab, setActiveTab }}>
8 <div className="tabs">
9 {children}
10 </div>
11 </TabsContext.Provider>
12 )
13}
14
15function TabList({ children }) {
16 return <div className="tab-list">{children}</div>
17}
18
19function Tab({ id, children }) {
20 const { activeTab, setActiveTab } = useContext(TabsContext)
21
22 return (
23 <button
24 className={`tab ${activeTab === id ? 'active' : ''}`}
25 onClick={() => setActiveTab(id)}
26 >
27 {children}
28 </button>
29 )
30}
31
32function TabPanels({ children }) {
33 return <div className="tab-panels">{children}</div>
34}
35
36function TabPanel({ id, children }) {
37 const { activeTab } = useContext(TabsContext)
38
39 if (activeTab !== id) return null
40 return <div className="tab-panel">{children}</div>
41}
42
43// Attach sub-components
44Tabs.List = TabList
45Tabs.Tab = Tab
46Tabs.Panels = TabPanels
47Tabs.Panel = TabPanel
48
49// Usage
50function App() {
51 return (
52 <Tabs defaultTab="tab1">
53 <Tabs.List>
54 <Tabs.Tab id="tab1">Tab 1</Tabs.Tab>
55 <Tabs.Tab id="tab2">Tab 2</Tabs.Tab>
56 <Tabs.Tab id="tab3">Tab 3</Tabs.Tab>
57 </Tabs.List>
58 <Tabs.Panels>
59 <Tabs.Panel id="tab1">Content 1</Tabs.Panel>
60 <Tabs.Panel id="tab2">Content 2</Tabs.Panel>
61 <Tabs.Panel id="tab3">Content 3</Tabs.Panel>
62 </Tabs.Panels>
63 </Tabs>
64 )
65}Why Not Inheritance?
jsx
1// ❌ Don't do this - inheritance
2class Button extends React.Component {
3 // Base button
4}
5
6class PrimaryButton extends Button {
7 // Extends base - creates tight coupling
8}
9
10// ✅ Do this - composition
11function Button({ variant, children, ...props }) {
12 return <button className={`btn btn-${variant}`} {...props}>{children}</button>
13}
14
15function PrimaryButton(props) {
16 return <Button variant="primary" {...props} />
17}Composition keeps components flexible and reusable!
