Destructuring Props
When React calls your component it passes a single object — props — containing every attribute you specified in JSX. You can work with that object directly (props.name, props.age), but destructuring the props in the function signature is almost always cleaner, shorter, and more readable.
The Evolution: props.x → Destructured
Here is the same component written three ways, from least to most idiomatic:
// Style 1 — receiving the whole props object
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.role} · {props.department}</p>
<span>{props.isActive ? 'Active' : 'Inactive'}</span>
</div>
)
}
// Style 2 — destructure inside the body
function UserCard(props) {
const { name, role, department, isActive } = props
return (
<div>
<h2>{name}</h2>
<p>{role} · {department}</p>
<span>{isActive ? 'Active' : 'Inactive'}</span>
</div>
)
}
// Style 3 — destructure in the signature (most common, recommended)
function UserCard({ name, role, department, isActive }) {
return (
<div>
<h2>{name}</h2>
<p>{role} · {department}</p>
<span>{isActive ? 'Active' : 'Inactive'}</span>
</div>
)
}Alias Renaming with Destructuring
Sometimes a prop name conflicts with a local variable, a JavaScript
reserved word, or you simply want a more descriptive name inside the
component. Use the { propName: localName } syntax to rename during
destructuring:
function ProductItem({
// Rename 'name' to 'productName' to avoid shadowing a common variable
name: productName,
// Rename 'type' to 'productType' — 'type' is an HTML attribute on some elements
type: productType,
price,
}) {
return (
<li>
<strong>{productName}</strong>
<em> ({productType})</em>
<span> — ${price.toFixed(2)}</span>
</li>
)
}
// Called just like any other component:
<ProductItem name="Laptop Pro" type="Electronics" price={1299.99} />Nested Destructuring
If a prop is itself an object, you can destructure it inline. This works well for structured data like addresses or coordinates:
// Without nested destructuring:
function AddressLabel({ address }) {
return <p>{address.street}, {address.city}, {address.country}</p>
}
// With nested destructuring — more concise inside the body:
function AddressLabel({ address: { street, city, country } }) {
return <p>{street}, {city}, {country}</p>
}
// Usage:
const location = { street: '123 Main St', city: 'Toronto', country: 'CA' }
<AddressLabel address={location} />Rest Spread: Collecting Remaining Props
The rest syntax (...rest) collects all props that were not explicitly destructured into a new object. This is invaluable when building wrapper components that need to forward unknown props to an underlying element:
// A custom input that adds a label but forwards everything else to <input>
function LabeledInput({ label, id, ...rest }) {
return (
<div className="field">
<label htmlFor={id}>{label}</label>
<input id={id} {...rest} />
</div>
)
}
// The caller passes standard <input> attributes — they all flow through via rest:
<LabeledInput
label="Email"
id="email"
type="email"
placeholder="you@example.com"
required
autoComplete="email"
/>// A styled button wrapper that intercepts 'variant' but passes the rest
function Button({ variant = 'primary', className = '', ...rest }) {
const cls = `btn btn--${variant} ${className}`.trim()
return <button className={cls} {...rest} />
}
// Works with any <button> prop automatically:
<Button variant="danger" onClick={handleDelete} disabled={isLoading}>
Delete
</Button>TypeScript: Typing Destructured Props
In TypeScript, define a Props interface and annotate the parameter:
import { ButtonHTMLAttributes } from 'react'
// Extend a native HTML element's attributes for full type safety on rest props:
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger'
}
function Button({ variant = 'primary', className = '', ...rest }: ButtonProps) {
const cls = `btn btn--${variant} ${className}`.trim()
return <button className={cls} {...rest} />
}Combining All Techniques
interface CardProps {
title: string
subtitle?: string
// Rename for internal clarity:
variant?: 'elevated' | 'outlined' | 'flat'
children: React.ReactNode
className?: string
}
function Card({
title,
subtitle = '',
variant: cardVariant = 'elevated', // rename + default
children,
className = '',
...rest // forward any extra div props
}: CardProps) {
return (
<div
className={`card card--${cardVariant} ${className}`.trim()}
{...rest}
>
<div className="card-header">
<h3>{title}</h3>
{subtitle && <p className="card-subtitle">{subtitle}</p>}
</div>
<div className="card-body">{children}</div>
</div>
)
}Destructure in the signature — it doubles as documentation and removes repetitive
props.prefixesAlias with
{ name: localName }when you need a different name inside the componentUse
...restin wrapper components to forward unknown props to underlying elementsCombine with defaults —
{ size = "md", ...rest }is idiomatic and expressiveExtend native types in TypeScript (
HTMLButtonAttributes,HTMLInputAttributes) to get free type-checking on forwarded props