ReactProps Are Read-Only

Props Are Read-Only

One of React's core rules is simple but non-negotiable: a component must never modify its own props. Props are read-only. They represent a snapshot of data delivered from the parent at the moment of rendering, and the child has no authority to change them.

The Pure Function Analogy

React components are expected to behave like pure functions with respect to their props. A pure function:

  • Returns the same output given the same inputs

  • Does not modify its input arguments

  • Has no side effects that affect the outside world

Consider this mathematical function: f(x) = x * 2. No matter how many times you call it with x = 5 it always returns 10, and it never changes the value stored in x. React components should work the same way with respect to their props.

JS
// Pure function — same input always gives the same output, nothing mutated
function double(x) {
  return x * 2
}

// Impure function — mutates its argument
function addTax(order) {
  order.total = order.total * 1.1  // ✗ mutates the input
  return order
}
What Happens When You Mutate Props
Warning
Mutating props does not immediately throw an error in React, but it causes subtle, hard-to-debug bugs. React caches props between renders, and mutating them confuses React's comparison logic — leading to stale UI, missed updates, or phantom re-renders.

JSX
// ✗ WRONG — mutating the props object
function DiscountBadge({ product }) {
  // This appears to work locally, but it corrupts the parent's data
  product.price = product.price * 0.9   // ✗ never do this
  return <span>Sale: ${product.price.toFixed(2)}</span>
}

// The parent component's product object is now permanently changed!
// Any other component reading product.price sees the wrong value.

Because JavaScript passes objects by reference, mutating product inside DiscountBadge modifies the same object the parent component holds in memory. Every component that reads that object now sees the corrupted value. React had no chance to reconcile the change — from React's perspective, nothing changed (the reference is the same).

The Correct Approach: Derive, Don't Mutate

Instead of modifying the prop, compute a new value and use it locally:

JSX
// ✓ CORRECT — derive a new value, leave the prop untouched
function DiscountBadge({ product }) {
  const salePrice = product.price * 0.9   // local derived value
  return <span>Sale: ${salePrice.toFixed(2)}</span>
}

// product.price is still the original value — parent is unaffected
Note
"Derive, don't mutate" is a general principle in React. When you need a transformed version of a prop, create a new variable. Keep the original prop pristine.
I Need to Change This Prop — What Should I Do?

When a child component genuinely needs to change data that came from a parent, the answer is always one of two patterns:

  • Lift state up — move the data into the parent as state, and pass a setter function down as a prop. The child calls the setter; the parent updates state; props flow back down.

  • Local copy — if the modification is purely presentational (the parent does not need to know), copy the value into local state with useState and modify the copy.

Pattern 1: Lift State Up

JSX
// The parent owns the data as state and passes a callback down
function ProductPage() {
  const [quantity, setQuantity] = useState(1)

  return (
    <div>
      <p>Quantity: {quantity}</p>
      <QuantityPicker value={quantity} onChange={setQuantity} />
    </div>
  )
}

// The child reads the value from props and calls the callback to request a change
function QuantityPicker({ value, onChange }) {
  return (
    <div>
      <button onClick={() => onChange(value - 1)} disabled={value <= 1}>-</button>
      <span>{value}</span>
      <button onClick={() => onChange(value + 1)}>+</button>
    </div>
  )
}

// QuantityPicker never touches 'value' directly —
// it only calls onChange and lets the parent decide the new state
Pattern 2: Local State Copy

JSX
// The parent doesn't care about internal edits until 'Save' is clicked
function ProfileEditor({ user }) {
  // Copy the prop into local state — edits stay local until saved
  const [localName, setLocalName] = useState(user.name)

  function handleSave() {
    // Parent learns about the change only via this callback
    saveToServer({ ...user, name: localName })
  }

  return (
    <form>
      <input
        value={localName}
        onChange={(e) => setLocalName(e.target.value)}
      />
      <button type="button" onClick={handleSave}>Save</button>
    </form>
  )
}

// user.name (the prop) is never touched — localName is a separate piece of state
Tip
Initializing `useState` from a prop (as above) is a valid and common pattern, but remember: if the parent later changes the `user.name` prop, the local state does NOT automatically update. This is intentional — you are making a controlled copy. If you need the local state to stay in sync, add a `useEffect` or use a `key` prop to reset the component.
Why This Rule Makes Apps More Predictable

The immutability of props is what makes React's one-way data flow so powerful. When you see a component rendering something unexpected, you know exactly where to look: follow the props up the tree to find the parent that owns the data. If props were mutable, any component anywhere could silently corrupt the data, and debugging would become a nightmare.

  • Predictability — the same props always produce the same render output

  • Traceability — data changes can only come from state updates, which have a clear owner

  • Testability — pure components are trivially unit-tested by calling them with different props

  • Concurrency safety — React 18+ may render the same component multiple times; mutation would cause race conditions