Reusable components are the building blocks of every React app. Instead of rewriting the same HTML over and over, we’ll build beautiful, typed, reusable components. By the end, you’ll:
- Understand how to create typed props for UI elements.
- Use generics for flexible components.
- Apply the compound component pattern for advanced flexibility.
🎯 1. Why Reusable Components?
Imagine you need 20 buttons across your app. Instead of copy-pasting <button> with styles, you:
- Create a single Button component.
- Style it once.
- Add props for variations (primary, secondary, etc.).
- Reuse it everywhere with type safety.
🧩 2. Reusable Button Component
// src/components/ui/Button.tsx
import { ButtonHTMLAttributes } from 'react';
import clsx from 'clsx';
// Define the variants our button can take
type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonProps = {
variant?: ButtonVariant; // style variant
isLoading?: boolean; // show spinner
} & ButtonHTMLAttributes<HTMLButtonElement>; // inherit all native button props
export function Button({
variant = 'primary',
isLoading = false,
className,
children,
...props
}: ButtonProps) {
const base = 'px-4 py-2 rounded font-medium';
const styles = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
return (
<button
className={clsx(base, styles[variant], className)}
disabled={isLoading || props.disabled}
{...props}
>
{isLoading ? '⏳ Loading…' : children}
</button>
);
}
👉 Key points:
ButtonHTMLAttributes<HTMLButtonElement>→ inherits all normal<button>props (onClick, disabled, etc.).clsx→ helps combine class names.variant→ only accepts'primary' | 'secondary' | 'danger'.
Usage:
<Button variant="primary">Save</Button>
<Button variant="danger" isLoading>Delete</Button>
🧩 3. Reusable Input Component
// src/components/ui/Input.tsx
import { InputHTMLAttributes } from 'react';
import clsx from 'clsx';
type InputProps = {
label?: string; // optional label
error?: string; // show error message
} & InputHTMLAttributes<HTMLInputElement>;
export function Input({ label, error, className, ...props }: InputProps) {
return (
<div className="flex flex-col gap-1">
{label && <label className="font-medium">{label}</label>}
<input
className={clsx(
'border rounded px-3 py-2',
error ? 'border-red-500' : 'border-gray-300',
className,
)}
{...props}
/>
{error && <span className="text-red-500 text-sm">{error}</span>}
</div>
);
}
Usage:
<Input label="Username" placeholder="Enter your name" />
<Input label="Email" type="email" error="Invalid email" />
👉 Key points:
- Inherits all
<input>props. - Adds
labelanderrorprops for accessibility & validation.
🧩 4. Modal Component (with Compound Pattern)
A modal often has multiple subcomponents: header, body, footer. Instead of passing a huge list of props, we can use the compound component pattern.
// src/components/ui/Modal.tsx
import { ReactNode } from 'react';
type ModalProps = {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
};
export function Modal({ isOpen, onClose, children }: ModalProps) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center">
<div className="bg-white rounded-lg p-6 w-96 relative">
<button className="absolute top-2 right-2" onClick={onClose}>❌</button>
{children}
</div>
</div>
);
}
// Subcomponents
type ModalSectionProps = { children: ReactNode };
export const ModalHeader = ({ children }: ModalSectionProps) => <h2 className="text-lg font-bold mb-2">{children}</h2>;
export const ModalBody = ({ children }: ModalSectionProps) => <div className="mb-4">{children}</div>;
export const ModalFooter = ({ children }: ModalSectionProps) => <div className="flex justify-end gap-2">{children}</div>;
Usage:
<Modal isOpen={true} onClose={() => console.log('close')}>
<ModalHeader>Confirm Delete</ModalHeader>
<ModalBody>Are you sure you want to delete this item?</ModalBody>
<ModalFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="danger">Delete</Button>
</ModalFooter>
</Modal>
👉 Benefits:
- Clean separation of header/body/footer.
- Easier to compose UI.
🎨 5. Visual Appeal (Blog Styling Tip)
When blogging, show before & after screenshots:
- Before → raw
<button>HTML - After → styled
<Button>component
Add code playgrounds (like CodeSandbox/StackBlitz) for readers to play.
✅ Wrap-Up
In Part 3, you learned:
- How to build typed reusable components.
- How to extend native props with
HTMLAttributes. - How to add variants with union types.
- How to use the compound component pattern (Modal).
👉 Next: We’ll learn state management — starting with useState, useReducer, and building typed custom hooks to manage app logic.
Comments
Post a Comment