ReactDefault Prop Values

Default Prop Values

Not every prop needs to be required. When a prop has a sensible fallback value, you can make it optional and provide a default so that callers only pass it when they want to override the default behaviour. This is called graceful degradation — the component works correctly even when called with the bare minimum of information.

ES6 Default Parameters in Destructuring

The modern and recommended way to set default prop values is with ES6 default parameter syntax directly in the destructuring assignment. Anything you can write in a regular function's parameter list works here:

JSX
// Default values declared right in the function signature:
function Avatar({ name = 'Guest', size = 40, shape = 'circle' }) {
  return (
    <img
      src={`https://api.dicebear.com/7.x/initials/svg?seed=${name}`}
      alt={name}
      width={size}
      height={size}
      style={{ borderRadius: shape === 'circle' ? '50%' : 4 }}
    />
  )
}

// All three props are optional — all defaults kick in:
<Avatar />

// Override just the ones you care about:
<Avatar name="Alice" size={64} />
Note
When a parent passes `undefined` for a prop, React treats it the same as not passing the prop at all, so the default value is used. Passing `null` is different — `null` overrides the default and the prop receives `null`.
Before and After: Required vs Optional Props

See how adding defaults transforms a rigid component into a flexible one:

JSX
// BEFORE — every prop is required; forgetting one breaks the UI
function Button({ label, variant, size, disabled, onClick }) {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

// The caller must always provide every prop:
<Button label="Save" variant="primary" size="md" disabled={false} onClick={save} />

JSX
// AFTER — sensible defaults; only override what you need
function Button({
  label,
  variant = 'primary',
  size = 'md',
  disabled = false,
  onClick,
}) {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

// Minimal call — defaults handle the rest:
<Button label="Save" onClick={save} />

// Still easy to override any default:
<Button label="Delete" variant="danger" size="sm" onClick={handleDelete} />
TypeScript: Marking Props Optional

In TypeScript, pair the ? modifier in the interface with a default value in the destructuring. Props without a ? are still required, and TypeScript will error if they are omitted:

TSX
interface ButtonProps {
  label: string           // required — no default, no ?
  variant?: 'primary' | 'secondary' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  onClick?: () => void
}

function Button({
  label,
  variant = 'primary',
  size = 'md',
  disabled = false,
  onClick,
}: ButtonProps) {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}
Tip
A TypeScript prop typed as `boolean` (without `?`) must be explicitly passed. Typing it as `boolean | undefined` (or `boolean?`) and giving it a default of `false` is far more ergonomic for flag props.
Common Pattern: Boolean Props Default to false

Boolean feature-flag props almost always default to false so the component is in its simplest state by default and you "opt in" to the extra behaviour:

JSX
function Input({
  type = 'text',
  placeholder = '',
  disabled = false,   // off by default
  readOnly = false,   // off by default
  showLabel = true,   // on by default — note: not all booleans default false
  label,
}) {
  return (
    <div>
      {showLabel && <label>{label}</label>}
      <input
        type={type}
        placeholder={placeholder}
        disabled={disabled}
        readOnly={readOnly}
      />
    </div>
  )
}

// Simple usage — only pass what differs from the default:
<Input label="Email" type="email" placeholder="you@example.com" />

// Opt-in to disabled state:
<Input label="Username" disabled />
Default Values for Objects and Arrays

You can default an object or array prop, but be careful: if you define the default inside the function signature it creates a new reference every render, which can cause child re-renders. For objects and arrays define the default outside the component, or use useMemo:

JSX
// ✗ Creates a new array reference on every render
function TagList({ tags = [] }) { /* ... */ }

// ✓ Stable reference — define the default outside the component
const EMPTY_TAGS: string[] = []

function TagList({ tags = EMPTY_TAGS }) {
  return (
    <ul>
      {tags.map((tag) => <li key={tag}>{tag}</li>)}
    </ul>
  )
}
Warning
The new-reference-every-render problem only matters when the prop is passed into a `React.memo`-wrapped child or used in a `useEffect` dependency array. For most cases it is harmless, but the pattern of defining default objects/arrays outside the component is a good habit.
Legacy: defaultProps (Avoid in New Code)

Before ES6 default parameters, React offered a static defaultProps object on the component. This approach is now deprecated and will be removed in a future React version. Avoid it in new code:

JSX
// ✗ Legacy — deprecated in React 19, will be removed
function Button({ label, variant }) {
  return <button className={`btn--${variant}`}>{label}</button>
}

Button.defaultProps = {
  variant: 'primary',
}

// ✓ Modern — use ES6 default parameters instead
function Button({ label, variant = 'primary' }) {
  return <button className={`btn--${variant}`}>{label}</button>
}
  • Use ES6 defaults in the destructuring — they are standard JavaScript, IDE-friendly, and work great with TypeScript

  • Boolean props almost always default to false so the component is opt-in

  • Optional string/number props benefit from a descriptive default (e.g. name = "Guest", size = "md")

  • Avoid defaultProps on function components — it is deprecated and will be removed

  • Object/array defaults should be defined outside the component or inside useMemo to avoid creating new references on every render