Course outline · 0% complete

0/28 lessons0%

Course overview →

Passing data with props

lesson 3-1 · ~10 min · 7/28

What was missing from those TaskItems

Lesson 2-3's two <TaskItem /> components shared one flaw: both rendered the exact same hard-coded text, with no way to give each one different data.

Every <TaskItem /> showed the same hard-coded "Water the plants". Reuse is only useful if each copy can display different data, since otherwise the component is a shorthand for one fixed piece of markup rather than a reusable piece of UI.

The plain-JavaScript card(title, body) from that same lesson already had the answer, because it took parameters. Props are how a JSX tag passes arguments to the function behind it, and that is this lesson's topic.

Passing data with props

Props (short for properties) are the inputs of a component. You pass them where you use the component, written like HTML attributes, and React collects them into a single object that becomes the function's first parameter:

function TaskItem(props) {
  return <li>{props.title}</li>;
}

function App() {
  return (
    <ul>
      <TaskItem title="Water the plants" />
      <TaskItem title="Pay rent" />
    </ul>
  );
}

<TaskItem title="Pay rent" /> makes React call TaskItem({ title: "Pay rent" }). Strings use quotes. Anything else, numbers, booleans, arrays, objects, functions, goes in braces:

<TaskItem title="Pay rent" done={true} priority={2} />
AppTaskItem{ title: "Water plants" }TaskItem{ title: "Pay rent" }title="Water plants"title="Pay rent"same component, different props, different output
Props flow down the tree. App renders TaskItem twice, each call receives its own props object, so one component produces two different list items.

Props with the JSX stripped away

A component is a function and props is just its argument object, which is easiest to see without any JSX in the way.

function TaskItem(props) {
  return "<li>" + props.title + (props.done ? " (done)" : "") + "</li>";
}

console.log(TaskItem({ title: "Water the plants", done: false }));
console.log(TaskItem({ title: "Pay rent", done: true }));

Output

<li>Water the plants</li>
<li>Pay rent (done)</li>

The JSX <TaskItem title="Pay rent" done={true} /> is exactly the second call here. That equivalence is the whole idea of props, since attributes in the tag become keys in one object, and the object becomes the function's first parameter.

One function call, two different outputs, driven only by the argument. That is UI = f(data) from lesson 1-2 at the level of a single component, and it is why the same TaskItem can appear a hundred times in a list.

The done flag controls a suffix through a ternary, which is the conditional-rendering shape again. Note that false produces the empty string rather than the word false, which matters because JSX has its own rules about falsy values that unit 5 covers.

Destructuring props

Writing props.title everywhere gets noisy. In Advanced JavaScript you learned object destructuring, and React code uses it in the parameter list almost universally:

function TaskItem({ title, done }) {
  return <li>{done ? "✓ " : ""}{title}</li>;
}

Same function, the { title, done } pattern pulls the fields out of the props object immediately. Read any real React codebase and this is the shape you will see.

Destructuring in the parameter list

Badge pulls label and count straight out of the props object in its signature, which is the shape almost all real React code uses.

function Badge({ label, count }) {
  return "<span>" + label + ": " + count + "</span>";
}

console.log(Badge({ label: "Inbox", count: 4 }));
console.log(Badge({ label: "Spam", count: 0 }));

Output

<span>Inbox: 4</span>
<span>Spam: 0</span>

The braces in function Badge({ label, count }) are a destructuring pattern rather than an object literal, so label and count are ordinary local variables inside the function and no props. prefix is needed anywhere.

The signature now documents the contract. Anyone reading the first line knows this component wants exactly two inputs, which is information the props version hides inside the body.

Note the second call passes count: 0 and the output shows the zero. Concatenation turns the number into "0" faithfully, and unit 5 shows the one place where a zero in JSX behaves surprisingly, which is inside a && expression.

The tradeoff worth knowing: destructuring loses access to the whole props object, so a component that needs to forward every prop onward keeps props or uses a rest pattern like ({ label, ...rest }). For everything else, the destructured form wins on readability.