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:
- Identify the UI Hierarchy:
- Break down the UI into a hierarchy of components.
- Build a Static Version:
- Create a static version of your app without any interactivity.
- Identify the Minimal Representation of UI State:
- Determine the minimal set of mutable state your app needs.
- Identify Where Your State Should Live:
- Decide which component should own the state based on where it is used.
- 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 toCartItem
andTotalPrice
.
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