Skip
Arish's avatar

16. Composition vs Inheritance


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!