Building Truly Reusable Components in React: Patterns & Best Practices
1. Embrace Component Composition Over Prop Explosion
When building a versatile UI element like a Modal, Card, or Notification banner, avoid passing all data and configurations via a massive list of props.
โ The Anti-Pattern: Config-Heavy Components
// Rigid: Hard to extend or customize without modifying internal component logic
<CustomCard
buttonText="Save Changes"
icon="user"
onButtonClick={handleSave}
showButton={true}
subtitle="Manage your profile details"
title="Account Settings"
variant="bordered"
/>
โ The Fix: Compound Components Pattern
By decomposing the layout into smaller, focused sub-components, you allow consumer code to structure the UI freely:
// Card.jsx
export function Card({ children, className = '' }) {
return (
<div
className={`bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6 shadow-sm ${className}`}
>
{children}
</div>
);
}
Card.Header = function CardHeader({ children }) {
return <div className="mb-4">{children}</div>;
};
Card.Title = function CardTitle({ children }) {
return <h3 className="text-lg font-bold text-slate-900 dark:text-white">{children}</h3>;
};
Card.Body = function CardBody({ children }) {
return <div className="text-sm text-slate-600 dark:text-slate-400">{children}</div>;
};
Card.Footer = function CardFooter({ children }) {
return <div className="mt-6 flex items-center justify-end gap-3">{children}</div>;
};
Usage:
<Card>
<Card.Header>
<Card.Title>Account Settings</Card.Title>
</Card.Header>
<Card.Body>
<p>Manage your profile details and security preferences.</p>
</Card.Body>
<Card.Footer>
<Button variant="secondary">Cancel</Button>
<Button onClick={handleSave}>Save Changes</Button>
</Card.Footer>
</Card>
2. Separate Logic from Presentation (Custom Hooks)
To ensure presentation components remain reusable across different projects or contexts, avoid embedding API fetching, complex calculations, or business logic inside the UI code. Extract operational behavior into Custom React Hooks:
// useToggle.js - Reusable behavioral hook
import { useState, useCallback } from 'react';
export function useToggle(initialState = false) {
const [value, setValue] = useState(initialState);
const toggle = useCallback(() => setValue((prev) => !prev), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
Now, any UI element (accordions, dropdown menus, modals, tooltips) can reuse this state management routine without duplication.
3. Leverage Polymorphic Components (as Prop Pattern)
Sometimes you need a component to render with different underlying HTML semantics while retaining identical design tokens and styling rules. For instance, a component might need to render as an HTML <button>, an anchor tag for external links, or a React Router <Link>.
import React from 'react';
export function Button({ as: Component = 'button', children, className = '', ...props }) {
const baseStyles =
"inline-flex items-center justify-center px-4 py-2 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-indigo-600 hover:bg-indigo-700 text-white";
return (
<Component className={`${baseStyles} ${className}`} {...props}>
{children}
</Component>
);
}
Usage:
// Standard HTML Button
<Button onClick={handleClick}>Submit Form</Button>
// Rendered as an Anchor Link
<Button as="a" href="https://example.com" target="_blank">External Link</Button>
Allow Native HTML Attribute Spreading
Never lock down your reusable components by forgetting standard HTML attributes like disabled, type, aria-*, or onFocus. Always spread rest parameters (...props) onto the primary underlying DOM element.
// InputField.jsx
export function InputField({ label, id, error, className = '', ...props }) {
return (
<div className="flex flex-col gap-1.5 w-full">
{label && (
<label htmlFor={id} className="text-sm font-medium text-slate-700 dark:text-slate-300">
{label}
</label>
)}
<input
id={id}
className={`px-3 py-2 rounded-lg border text-sm transition-colors ${
error
? 'border-rose-500 focus:ring-rose-500'
: 'border-slate-300 focus:border-indigo-500 dark:border-slate-700'
} ${className}`}
{...props} // Spreads native props: placeholder, onChange, value, required, disabled, etc.
/>
{error && <span className="text-xs text-rose-500">{error}</span>}
</div>
);
}
Core Checklist for Component Reusability
- Single Responsibility: Does this component do strictly one job?
- Flexible Composition: Can consumer components inject custom HTML/components as children without breaking layout rules?
- Encapsulated Styling: Are utility classes or CSS modules scoped so they don't bleed into global styles?
- Accessibility First: Are aria- tags and key listeners preserved via native prop spreading?
Need Custom Enterprise UI Frameworks & Web Platforms?
Architecting clean, scalable frontend systems and maintainable component libraries requires experienced software engineering.
๐ Partner with Software Solutions for custom full-stack software development, React/Next.js architectures, modern enterprise applications, and cloud software engineering designed to scale your products effortlessly.
Comments
No comments yet. Start the discussion.