Course outline · 0% complete

0/28 lessons0%

Course overview →

Props are read-only: component contracts

lesson 3-3 · ~10 min · 9/28

Props are read-only

A hard rule: a component must never change its props. Props flow downward from parent to child, and the child treats them as read-only input. This is what makes components predictable: same props in, same UI out, every time. Functions with that property are called pure, an idea you met with map callbacks in Advanced JavaScript.

function Price({ amount }) {
  amount = amount * 1.1;   // WRONG: mutating input
  return <p>{amount}</p>;
}

function Price({ amount }) {
  const withTax = amount * 1.1;   // RIGHT: derive a new value
  return <p>{withTax}</p>;
}

Deriving new values from props is normal and encouraged. Reassigning or mutating the props themselves is not.

If a child needs to change data, the parent passes down a function prop and the child calls it. We will build that pattern in Unit 4 with state, and again in Unit 8 when we lift state up.

Default values

A component's props form its contract: which inputs it accepts, which are required, what happens when one is missing. Destructuring defaults (Advanced JavaScript again) handle the missing case cleanly:

function Button({ label = "Click me", kind = "default" }) {
  return <button className={kind}>{label}</button>;
}

<Button label="Save" kind="primary" />   // uses both
<Button label="Cancel" />                 // kind falls back
<Button />                                // both fall back

Defaults make components safe to use with partial props and document the contract right in the signature.

Defaults in the signature

Button gives both props defaults, so it renders sensibly with full props, partial props, or none at all.

function Button({ label = "Click me", kind = "default" }) {
  return "<button class=\"" + kind + "\">" + label + "</button>";
}

console.log(Button({ label: "Save", kind: "primary" }));
console.log(Button({ label: "Cancel" }));
console.log(Button({}));

Output

<button class="primary">Save</button>
<button class="default">Cancel</button>
<button class="default">Click me</button>

Destructuring defaults go right in the parameter list, as { label = "Click me", kind = "default" }, which is the Advanced JavaScript syntax applied to a component signature.

A default only kicks in when the property is missing or explicitly undefined. That distinction matters, since passing kind={null} or kind={""} skips the default and renders an empty class, which is a real source of confusion the first time it happens.

The signature now serves as documentation. A reader learns from one line that this component accepts two optional props and what each falls back to, without reading the body or hunting for a separate defaults object.

Note the escaped quotes in the string, written as \", which are only needed because this plain-JavaScript version builds HTML by hand. The JSX version writes className={kind} and has no quoting problem at all.

A reassigned prop

function Discount({ price }) {
  price = price - 5;
  return <p>Now only {price}</p>;
}

The bug is that the component reassigns its prop instead of deriving a new value, as in const salePrice = price - 5.

Props are read-only input. Reassigning price happens to work here, since destructuring created a local variable, and it breaks the purity contract and confuses anyone reading the code, because the name price now means two different things in one function.

The harder failure comes with objects and arrays. Writing user.name = "x" or items.push(newItem) mutates data the parent still owns, so the parent's copy changes without the parent knowing, and React may not re-render because the parent's own value looks unchanged. Unit 4's lesson on objects and arrays in state covers that trap in detail.

The fix is one const and costs nothing:

StyleReads asSafe
price = price - 5overwrite the inputno
const salePrice = price - 5derive a new valueyes

The derived version also names the concept, since salePrice says what the number is, and that is a real readability gain over a variable whose meaning changed halfway down the function.