ReactDestructuring Props

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:

JSX
// 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>
  )
}
Note
All three styles produce identical output. Style 3 is the community standard because the function signature acts as self-documenting API for the component — you can see the complete prop list at a glance.
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:

JSX
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:

JSX
// 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} />
Tip
Nested destructuring looks clean but can be confusing when deeply nested. For objects more than two levels deep, consider destructuring inside the function body instead, or accepting a flat set of props.
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:

JSX
// 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"
/>

JSX
// 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:

TSX
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} />
}
Note
Extending `ButtonHTMLAttributes<HTMLButtonElement>` gives TypeScript full knowledge of every valid `<button>` prop so it can autocomplete and type-check `onClick`, `disabled`, `type`, etc. without you listing them manually.
Combining All Techniques

TSX
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. prefixes

  • Alias with { name: localName } when you need a different name inside the component

  • Use ...rest in wrapper components to forward unknown props to underlying elements

  • Combine with defaults{ size = "md", ...rest } is idiomatic and expressive

  • Extend native types in TypeScript (HTMLButtonAttributes, HTMLInputAttributes) to get free type-checking on forwarded props