Composition vs Inheritance

Introduction

React is built on the philosophy of building user interfaces using components. One of the key decisions developers face when designing React applications is how to reuse code effectively. In traditional object-oriented programming (OOP), inheritance is commonly used for code reuse. However, React encourages composition over inheritance as the preferred pattern for creating flexible, maintainable, and scalable components.

This post explores the concepts of composition and inheritance, their differences, advantages, and best practices in React. We will cover real-world examples, patterns, and guidelines to help you make the right choice when building React applications.


Understanding Inheritance in Traditional Programming

Inheritance is an OOP concept where one class derives from another class, inheriting its properties and methods. It allows code reuse but introduces tight coupling between parent and child classes.

Example in JavaScript

class Vehicle {
  start() {
console.log("Vehicle started");
} } class Car extends Vehicle { drive() {
console.log("Car is driving");
} } const myCar = new Car(); myCar.start(); // Vehicle started myCar.drive(); // Car is driving

Inheritance works well in OOP, but in React, it is less favored because it can lead to rigid component hierarchies and make components harder to reuse flexibly.


Understanding Composition in React

Composition is a pattern where components contain other components or accept children and props to assemble complex UIs. Instead of inheriting behavior, components compose behavior by combining smaller, independent components.

Example: Basic Composition

function Card({ title, children }) {
  return (
<div className="card">
  <h2>{title}</h2>
  <div>{children}</div>
</div>
); } function App() { return (
<Card title="Profile">
  <p>Name: John Doe</p>
  <p>Email: [email protected]</p>
</Card>
); }

Here, the Card component is composed with children. You can reuse it for multiple purposes by passing different children.


Why React Prefers Composition Over Inheritance

  1. Flexibility: Composition allows components to be combined in various ways, while inheritance locks behavior in a hierarchy.
  2. Reusability: Small, composable components can be reused across the application without creating deep hierarchies.
  3. Maintainability: Changes in one component do not break the behavior of others as long as props and children are used correctly.
  4. Simplicity: Composition avoids the complexity of managing parent-child class relationships.
  5. Aligns with React Philosophy: React’s one-way data flow and declarative UI design are better suited to composition.

Comparison Between Composition and Inheritance

FeatureInheritanceComposition
ApproachHierarchical, class-basedModular, component-based
ReusabilityLimited, tightly coupledHigh, flexible and decoupled
FlexibilityLess flexible, rigid hierarchiesHighly flexible, mix and match
ComplexityCan become complex with deep hierarchiesSimple, promotes modularity
React AlignmentNot recommendedRecommended

Using Props for Composition

Props are fundamental to composition. By passing props, components can behave differently without inheriting from a parent.

Example: Passing Props

function Button({ label, onClick, style }) {
  return (
<button style={style} onClick={onClick}>
  {label}
</button>
); } function App() { return (
<div>
  <Button label="Save" onClick={() => alert("Saved!")} style={{ color: 'green' }} />
  <Button label="Delete" onClick={() => alert("Deleted!")} style={{ color: 'red' }} />
</div>
); }

Using Children for Composition

The children prop allows components to wrap other components and display dynamic content, making them highly reusable.

function Modal({ title, children }) {
  return (
<div className="modal">
  <h2>{title}</h2>
  <div>{children}</div>
</div>
); } function App() { return (
<Modal title="User Info">
  <p>Name: Alice</p>
  <p>Email: [email protected]</p>
</Modal>
); }

Higher-Order Components (HOCs)

Higher-order components are functions that take a component and return a new component, allowing behavior reuse without inheritance.

Example: HOC for Logging

function withLogging(WrappedComponent) {
  return function(props) {
console.log("Rendering:", WrappedComponent.name);
return <WrappedComponent {...props} />;
}; } function Button({ label }) { return <button>{label}</button>; } const ButtonWithLogging = withLogging(Button); function App() { return <ButtonWithLogging label="Click Me" />; }

HOCs are a form of composition because they wrap and enhance existing components.


Render Props Pattern

Render props allow passing a function as a prop to control what a component renders, enabling flexible composition.

Example: Render Props

function DataProvider({ render }) {
  const data = ["Apple", "Banana", "Cherry"];
  return render(data);
}

function App() {
  return (
&lt;DataProvider render={(fruits) =&gt; (
  &lt;ul&gt;
    {fruits.map((fruit, index) =&gt; &lt;li key={index}&gt;{fruit}&lt;/li&gt;)}
  &lt;/ul&gt;
)} /&gt;
); }

Avoiding Inheritance in React

Although React supports extending classes, it is rarely necessary. Instead of creating a base component and extending it, you should:

  • Create small, focused components.
  • Combine components using composition.
  • Share behavior using HOCs, render props, or custom hooks.

Example: What Not to Do

class BaseButton extends React.Component {
  render() {
return &lt;button&gt;{this.props.label}&lt;/button&gt;;
} } class SaveButton extends BaseButton { render() {
return &lt;button style={{ color: 'green' }}&gt;{this.props.label}&lt;/button&gt;;
} }

This approach tightly couples components and limits flexibility. Composition is a better choice.


Custom Hooks for Composition

React Hooks provide a function-based way to reuse behavior across components.

Example: useCounter Hook

import { useState } from 'react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  return { count, increment, decrement };
}

function Counter() {
  const { count, increment, decrement } = useCounter(0);
  return (
&lt;div&gt;
  &lt;p&gt;Count: {count}&lt;/p&gt;
  &lt;button onClick={increment}&gt;+&lt;/button&gt;
  &lt;button onClick={decrement}&gt;-&lt;/button&gt;
&lt;/div&gt;
); }

Custom hooks are a modern form of composition for sharing stateful logic.


Real-World Example: Building a Dashboard

Consider a dashboard with multiple widgets. Using composition:

function Widget({ title, children }) {
  return (
&lt;div className="widget"&gt;
  &lt;h3&gt;{title}&lt;/h3&gt;
  &lt;div&gt;{children}&lt;/div&gt;
&lt;/div&gt;
); } function Dashboard() { return (
&lt;div className="dashboard"&gt;
  &lt;Widget title="User Stats"&gt;
    &lt;p&gt;Active Users: 150&lt;/p&gt;
  &lt;/Widget&gt;
  &lt;Widget title="Revenue"&gt;
    &lt;p&gt;$5000 this month&lt;/p&gt;
  &lt;/Widget&gt;
  &lt;Widget title="Notifications"&gt;
    &lt;ul&gt;
      &lt;li&gt;New signup: John&lt;/li&gt;
      &lt;li&gt;Server maintenance at 8 PM&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/Widget&gt;
&lt;/div&gt;
); }

Each widget is independent and reusable, and behavior can be added without modifying existing components.


Best Practices for Composition in React

  1. Keep components small and focused.
  2. Use props and children for flexible configuration.
  3. Use custom hooks for reusable stateful logic.
  4. Avoid deep inheritance hierarchies.
  5. Use HOCs and render props for shared behavior.
  6. Favor composition over inheritance for maintainability and flexibility.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *