#9 Composition vs. Inheritance in React

#9 Composition vs. Inheritance in React

In React, composition and inheritance are two fundamental design patterns for creating reusable components. React emphasizes composition over inheritance to achieve greater flexibility and code reuse. This article will explore containment, specialization, and the concept of designing components based on composition.

Containment

Containment refers to the practice of using props to pass children to a component. This pattern allows you to create flexible components that can encapsulate other components.

Example:

import React from 'react';

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

export default WelcomeDialog;

In this example, FancyBorder is a container component that renders its children inside a styled div. WelcomeDialog uses FancyBorder to wrap its content.

Specialization

Specialization involves creating more specific versions of a component. This pattern allows you to extend the functionality of a component without modifying its implementation.

Example:

import React from 'react';

function Dialog(props) {
  return (
    <div className="Dialog">
      <div className="Dialog-title">
        {props.title}
      </div>
      <div className="Dialog-message">
        {props.message}
      </div>
    </div>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

export default WelcomeDialog;

In this example, Dialog is a general-purpose component that can be specialized by passing different props. WelcomeDialog is a specific instance of Dialog with predefined title and message props.

Thinking in React: Designing Components Based on Composition

When designing React applications, it’s essential to think in terms of composition. Breaking down your UI into smaller, reusable components makes your code more manageable and easier to maintain.

Steps for Designing Components:

  1. Identify the UI Hierarchy:
    • Break down the UI into a hierarchy of components.
  2. Build a Static Version:
    • Create a static version of your app without any interactivity.
  3. Identify the Minimal Representation of UI State:
    • Determine the minimal set of mutable state your app needs.
  4. Identify Where Your State Should Live:
    • Decide which component should own the state based on where it is used.
  5. Add Inverse Data Flow:
    • Pass down callbacks to children to update the state in the parent component.

Example:

Let’s design a simple shopping cart application.

Step 1: Identify the UI Hierarchy:

  • ShoppingCart
    • CartItem
    • TotalPrice

Step 2: Build a Static Version:

import React from 'react';

function CartItem(props) {
  return (
    <div>
      <span>{props.name}</span>
      <span>{props.price}</span>
    </div>
  );
}

function TotalPrice(props) {
  return (
    <div>Total: {props.total}</div>
  );
}

function ShoppingCart(props) {
  const items = [
    { id: 1, name: 'Apple', price: 1.00 },
    { id: 2, name: 'Banana', price: 0.50 }
  ];
  const total = items.reduce((sum, item) => sum + item.price, 0);

  return (
    <div>
      {items.map(item => (
        <CartItem key={item.id} name={item.name} price={item.price} />
      ))}
      <TotalPrice total={total} />
    </div>
  );
}

export default ShoppingCart;

Step 3: Identify the Minimal Representation of UI State:

  • The cart items and total price are the minimal state.

Step 4: Identify Where Your State Should Live:

  • The state should live in the ShoppingCart component as it is passed down to CartItem and TotalPrice.

Step 5: Add Inverse Data Flow:

For simplicity, let’s assume we have a button to remove items.

import React, { useState } from 'react';

function CartItem(props) {
  return (
    <div>
      <span>{props.name}</span>
      <span>{props.price}</span>
      <button onClick={() => props.onRemove(props.id)}>Remove</button>
    </div>
  );
}

function TotalPrice(props) {
  return (
    <div>Total: {props.total}</div>
  );
}

function ShoppingCart() {
  const initialItems = [
    { id: 1, name: 'Apple', price: 1.00 },
    { id: 2, name: 'Banana', price: 0.50 }
  ];
  const [items, setItems] = useState(initialItems);

  const handleRemove = (id) => {
    setItems(items.filter(item => item.id ! = = id));
  };

  const total = items.reduce((sum, item) => sum + item.price, 0);

  return (
    <div>
      {items.map(item => (
        <CartItem key={item.id} id={item.id} name={item.name} price={item.price} onRemove={handleRemove} />
      ))}
      <TotalPrice total={total} />
    </div>
  );
}

export default ShoppingCart;

In this example, ShoppingCart manages the state of the cart items and handles the removal of items via the handleRemove function passed to CartItem.

Conclusion

React emphasizes composition over inheritance for building reusable and maintainable components. By using containment and specialization patterns, you can create flexible components that are easy to manage and extend. Thinking in terms of composition helps break down your UI into manageable pieces, making your React applications more efficient and scalable.

Stay tuned for more in-depth React tutorials!


Tags

#React #JavaScript #FrontendDevelopment #WebDevelopment #Composition #Inheritance #Containment #Specialization #ComponentDesign #ReactTutorial #Programming #Coding #SoftwareDevelopment #UIDevelopment #JSX

Leave a Reply