Reusability
Creating modular, flexible components that can be used across different contexts
Reusability in Leadline Architecture Design
Core Principle
Reusability focuses on creating code components that can be used multiple times across different parts of an application or even across different projects, reducing duplication and increasing development efficiency.
What is Reusability?
Reusability is the ability to use existing software components in new applications or contexts without modification, or with minimal changes. It's about designing flexible, modular code that solves problems in a general way.
"Don't Repeat Yourself (DRY)" - Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Benefits of Reusable Code
Faster Development
Leverage existing components to build new features quickly.
Consistency
Uniform behavior and appearance across the application.
Easier Maintenance
Fix bugs and add features in one place, benefiting all uses.
Cost Efficiency
Reduce development time and resources needed for new projects.
Types of Reusability
Code Reusability
Creating functions, classes, and modules that can be used in multiple contexts:
Utility Functions
// Reusable utility functions
export const formatCurrency = (amount, currency = "USD") => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency,
}).format(amount);
};
export const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
};
export const deepClone = (obj) => {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map((item) => deepClone(item));
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
};Class Components
// Reusable class with configurable behavior
class DataValidator {
constructor(rules = {}) {
this.rules = rules;
}
validate(data) {
const errors = [];
for (const [field, fieldRules] of Object.entries(this.rules)) {
const value = data[field];
if (fieldRules.required && !value) {
errors.push(`${field} is required`);
}
if (fieldRules.minLength && value.length < fieldRules.minLength) {
errors.push(`${field} must be at least ${fieldRules.minLength} characters`);
}
if (fieldRules.pattern && !fieldRules.pattern.test(value)) {
errors.push(`${field} format is invalid`);
}
}
return { isValid: errors.length === 0, errors };
}
}
// Usage
const userValidator = new DataValidator({
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
},
password: {
required: true,
minLength: 8,
},
});Component Reusability
Building UI components that can be used across different pages and applications:
// Reusable Button component
const Button = ({
variant = 'primary',
size = 'medium',
children,
onClick,
disabled = false,
...props
}) => {
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-600 text-white hover:bg-gray-700',
danger: 'bg-red-600 text-white hover:bg-red-700'
};
const sizeClasses = {
small: 'px-2 py-1 text-sm',
medium: 'px-4 py-2',
large: 'px-6 py-3 text-lg'
};
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
onClick={onClick}
disabled={disabled}
{...props}
>
{children}
</button>
);
};
// Usage across different contexts
<Button variant="primary" onClick={handleSave}>Save</Button>
<Button variant="danger" onClick={handleDelete}>Delete</Button>
<Button variant="secondary" size="small">Cancel</Button>Design Pattern Reusability
Implementing reusable architectural patterns:
// Factory Pattern for creating different types of loggers
class LoggerFactory {
static createLogger(type, config = {}) {
switch (type) {
case "console":
return new ConsoleLogger(config);
case "file":
return new FileLogger(config);
case "remote":
return new RemoteLogger(config);
default:
throw new Error(`Unknown logger type: ${type}`);
}
}
}
// Strategy Pattern for different payment methods
class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}
processPayment(amount, details) {
return this.strategy.process(amount, details);
}
}
const creditCardStrategy = {
process: (amount, details) => {
// Credit card processing logic
return { success: true, transactionId: "cc_123" };
},
};
const paypalStrategy = {
process: (amount, details) => {
// PayPal processing logic
return { success: true, transactionId: "pp_456" };
},
};Best Practices for Reusability
Single Responsibility Principle
Each component should have one clear purpose:
// ❌ Component doing too many things
const UserProfileManager = () => {
// Handles user data, validation, API calls, UI rendering
};
// ✅ Separated concerns
const UserProfile = ({ user }) => {
/* UI only */
};
const useUserData = () => {
/* Data management */
};
const useUserValidation = () => {
/* Validation logic */
};Configuration Over Convention
Make components configurable rather than hardcoded:
// ❌ Hardcoded behavior
const Modal = ({ children }) => {
return (
<div className="fixed inset-0 bg-black bg-opacity-50">
<div className="bg-white p-6 rounded max-w-md mx-auto mt-20">{children}</div>
</div>
);
};
// ✅ Configurable behavior
const Modal = ({
children,
isOpen = false,
onClose,
size = "medium",
backdrop = true,
className = "",
...props
}) => {
if (!isOpen) return null;
const sizeClasses = {
small: "max-w-sm",
medium: "max-w-md",
large: "max-w-lg",
full: "max-w-full",
};
return (
<div
className={`fixed inset-0 z-50 ${backdrop ? "bg-black bg-opacity-50" : ""}`}
onClick={onClose}
>
<div
className={`bg-white p-6 rounded ${sizeClasses[size]} mx-auto mt-20 ${className}`}
onClick={(e) => e.stopPropagation()}
{...props}
>
{children}
</div>
</div>
);
};Interface Standardization
Define clear interfaces for reusable components:
interface ButtonProps {
variant?: "primary" | "secondary" | "danger";
size?: "small" | "medium" | "large";
disabled?: boolean;
loading?: boolean;
onClick?: (event: MouseEvent) => void;
children: React.ReactNode;
className?: string;
}
interface ApiResponse<T> {
data: T;
success: boolean;
message?: string;
errors?: string[];
}Common Reusability Patterns
Higher-Order Components
Wrap components to add common functionality.
Render Props
Share code between components using props.
Custom Hooks
Extract and share stateful logic between components.
Composition
Build complex functionality from simple, reusable parts.
Avoid Over-Engineering: Don't create reusable components until you have at least 2-3 use cases. Premature abstraction can lead to overly complex and hard-to-maintain code.
Measuring Reusability
Consider these metrics to assess reusability:
- Code Duplication Ratio: Percentage of duplicated code in the codebase
- Component Usage Count: How many times each component is used
- Abstraction Level: Balance between specific functionality and general purpose
- API Stability: How often component interfaces change
Documentation for Reusability
Effective documentation is crucial for reusable components:
/**
* A flexible data table component with sorting, filtering, and pagination
*
* @param {Array} data - Array of objects to display
* @param {Array} columns - Column definitions with keys and headers
* @param {Object} options - Configuration options
* @param {boolean} options.sortable - Enable column sorting (default: true)
* @param {boolean} options.filterable - Enable row filtering (default: false)
* @param {number} options.pageSize - Number of rows per page (default: 10)
*
* @example
* <DataTable
* data={users}
* columns={[
* { key: 'name', header: 'Name' },
* { key: 'email', header: 'Email' }
* ]}
* options={{ sortable: true, pageSize: 25 }}
* />
*/
const DataTable = ({ data, columns, options = {} }) => {
// Implementation
};