Proper Prop Destructuring in React Components
Destructuring props incorrectly is a common source of bugs and maintenance headaches in React applications. Here’s how to handle prop destructuring properly to write cleaner, more reliable components.
Common Destructuring Mistakes
// ❌ Problematic patterns:
// 1. Not destructuring at all
function UserCard(props) {
return (
<div>
<h2>{props.user.name}</h2>
<p>{props.user.email}</p>
</div>
);
}
// 2. Partial destructuring
function Product({ product, onAddToCart }) {
return (
<div>
<h3>{product.name}</h3>
<p>{product.description}</p>
<button onClick={() => onAddToCart(product.id)}>
Add to Cart
</button>
</div>
);
}
// 3. Destructuring inside render
function Comment({ comment }) {
const { author, text } = comment;
return (
<div>
<strong>{author}</strong>: {text}
</div>
);
}
Correct Destructuring Patterns
1. Full Prop Destructuring
// ✅ Proper destructuring at parameter level
function UserCard({ user, onSelect, className = 'card' }) {
return (
<div className={className} onClick={() => onSelect(user.id)}>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
2. Nested Destructuring
// ✅ Clean nested destructuring
function Product({
product: {
id,
name,
description,
price
},
onAddToCart
}) {
return (
<div className="product">
<h3>{name}</h3>
<p>{description}</p>
<span>${price.toFixed(2)}</span>
<button onClick={() => onAddToCart(id)}>
Add to Cart
</button>
</div>
);
}
3. Default Values with Destructuring
// ✅ Default values in destructuring
function Avatar({
src = '/default-avatar.png',
alt = 'User avatar',
size = 'medium'
}) {
const sizeClasses = {
small: 'w-8 h-8',
medium: 'w-12 h-12',
large: 'w-16 h-16'
};
return (
<img
src={src}
alt={alt}
className={`rounded-full ${sizeClasses[size]}`}
/>
);
}
Advanced Destructuring Techniques
1. Rest Parameters for Props Forwarding
// ✅ Using rest parameters
function Button({ variant = 'primary', children, ...props }) {
const variantClasses = {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-200 text-black'
};
return (
<button
className={`px-4 py-2 rounded ${variantClasses[variant]}`}
{...props}
>
{children}
</button>
);
}
2. TypeScript with Destructuring
// ✅ TypeScript with destructuring
interface UserProfileProps {
user: {
id: string;
name: string;
email: string;
avatar?: string;
};
onEdit?: (id: string) => void;
className?: string;
}
function UserProfile({
user: { id, name, email, avatar = '/default-avatar.png' },
onEdit,
className = ''
}: UserProfileProps) {
return (
<div className={`profile-card ${className}`}>
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
{onEdit && <button onClick={() => onEdit(id)}>Edit</button>}
</div>
);
}
3. Aliasing Destructured Properties
// ✅ Renaming destructured variables
function MapMarker({
position: { lat: latitude, lng: longitude },
label
}) {
return (
<div className="marker">
<span>{label}</span>
<span>({latitude}, {longitude})</span>
</div>
);
}
Best Practices
- Destructure at the parameter level when possible
- Provide default values for optional props
- Use rest parameters for props forwarding
- Keep destructuring readable – avoid too many nested levels
- Document prop types with TypeScript or PropTypes
- Consider component reusability when designing prop interfaces
// Well-structured component example
function DataTable({
columns,
data,
loading = false,
onRowClick,
...tableProps
}) {
if (loading) return <LoadingSpinner />;
return (
<table {...tableProps}>
<thead>
<tr>
{columns.map(({ header, key }) => (
<th key={key}>{header}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row) => (
<tr key={row.id} onClick={() => onRowClick?.(row)}>
{columns.map(({ key }) => (
<td key={`${row.id}-${key}`}>{row[key]}</td>
))}
</tr>
))}
</tbody>
</table>
);
}
Common Pitfalls to Avoid
- Over-destructuring complex objects (keep it readable)
- Ignoring prop types (use TypeScript or PropTypes)
- Forgetting default values for optional props
- Mutating props (props should be treated as immutable)
- Deeply nested destructuring that makes code hard to follow
Remember that proper prop destructuring:
- Makes component APIs clearer
- Reduces verbosity in your component code
- Helps catch missing props early
- Improves code maintainability
- Works beautifully with TypeScript
Always aim for destructuring patterns that make your component’s interface obvious and your implementation clean.