A component that always shows the same thing is of limited use. Props make components flexible. Props (short for properties) are the inputs of a component, in the same way as the arguments of a function. The parent decides the values, and the component uses them to decide what to show. Props are the single most important idea in React after components themselves.
Passing and reading props
You pass props like HTML attributes. The component receives them as one object in its first parameter:
function Greeting(props) {
return <h2>Hello, {props.name} from {props.city}!</h2>;
}
function App() {
return (
<div>
<Greeting name="Asha" city="Pune" />
<Greeting name="Ravi" city="Delhi" />
</div>
);
}
export default App;Hello, Asha from Pune!
Hello, Ravi from Delhi!The same Greeting component shows two different greetings, because each tag passes different values. React calls Greeting({ name: "Asha", city: "Pune" }) and then Greeting({ name: "Ravi", city: "Delhi" }). That is all props are: an object holding what the parent wrote in the tag.
Destructuring props
Writing props.name over and over gets tedious. Because props is an object, you can destructure it right in the parameter list. This is the style you will see in nearly all React code:
function ProductCard({ name, price, inStock }) {
return (
<div style={{ border: "1px solid #ccc", padding: 8, margin: 8 }}>
<h3>{name}</h3>
<p>Price: ${price.toFixed(2)}</p>
<p>{inStock ? "In stock" : "Sold out"}</p>
</div>
);
}
function App() {
return (
<div>
<ProductCard name="Notebook" price={4.5} inStock={true} />
<ProductCard name="Backpack" price={39.99} inStock={false} />
</div>
);
}
export default App;Notebook
Price: $4.50
In stock
Backpack
Price: $39.99
Sold outThe curly braces in function ProductCard({ name, price, inStock }) are destructuring, not JSX: they pull three properties out of the props object. If a prop is not passed, its variable is undefined.
Strings versus braces
A prop value in quotes is always a string. To pass any other kind of value, use curly braces, which hand over a JavaScript expression:
| You write | The component receives |
|---|---|
price="10" |
the string "10" |
price={10} |
the number 10 |
inStock={true} or just inStock |
the boolean true |
tags={["new", "sale"]} |
an array |
user={{ name: "Asha" }} |
an object (the double braces are braces plus an object) |
onSelect={handleSelect} |
a function |
price={base * 1.18} |
the result of an expression |
Writing a prop name with no value, like <Button disabled />, is shorthand for disabled={true}. A classic bug is price="10" followed by arithmetic, which concatenates text instead of adding: "10" + 5 is "105".
function Total({ price, qty, taxable }) {
const total = price * qty * (taxable ? 1.18 : 1);
return <p>{typeof price}: {total.toFixed(2)}</p>;
}
function App() {
return (
<div>
<Total price={100} qty={3} taxable />
<Total price={100} qty={3} taxable={false} />
</div>
);
}
export default App;number: 354.00
number: 300.00Default values
Give a prop a default value in the destructuring. It is used when the parent does not pass the prop, or passes undefined:
function Badge({ label = "New", color = "#ffd45c" }) {
return <span style={{ background: color, padding: "2px 8px", borderRadius: 10, marginRight: 6 }}>{label}</span>;
}
function App() {
return (
<p>
<Badge />
<Badge label="Sale" />
<Badge label="Hot" color="#ff7a59" />
<Badge label={null} />
</p>
);
}
export default App;NewSaleHotThe last badge passes null, and a default does not replace null (only undefined), so the label is empty. Keep that in mind when data comes from a server.
Passing objects and the spread shortcut
When you have many props, or an object with exactly the right shape, pass the object whole and destructure inside:
function UserCard({ user }) {
const { name, role, skills } = user;
return (
<div>
<strong>{name}</strong> ({role}): {skills.join(", ")}
</div>
);
}
function Link({ href, children, ...rest }) {
return <a href={href} {...rest}>{children}</a>;
}
function App() {
const user = { name: "Asha", role: "developer", skills: ["HTML", "CSS", "React"] };
const linkProps = { href: "#docs", title: "Open the docs", target: "_self" };
return (
<div>
<UserCard user={user} />
<Link {...linkProps}>Documentation</Link>
</div>
);
}
export default App;Asha (developer): HTML, CSS, React
DocumentationTwo spread tricks appear here. <Link {...linkProps}> passes every property of the object as a separate prop. And ...rest in the parameter list collects all the props that were not named, so that Link can forward them to the real <a>. This is how you build a thin wrapper around an HTML element without repeating each attribute. Use it with care: forwarding everything blindly can pass props you did not intend.
Passing functions as props
Props can be functions, which is how a child tells its parent that something happened (a click, a choice). The child calls the function; the parent decides what to do:
function ConfirmButton({ label, onConfirm }) {
return <button onClick={onConfirm}>{label}</button>;
}
function App() {
function handleConfirm() {
document.getElementById("note").textContent = "Confirmed!";
}
return (
<div>
<ConfirmButton label="Confirm order" onConfirm={handleConfirm} />
<p id="note">Waiting...</p>
</div>
);
}
export default App;Confirm order
Confirmed!(This example writes directly to the page to keep it short. In a real app you would store the message in state, which the State lessons cover.) By convention, props that carry event handlers are named onSomething (onConfirm, onChange) and the functions that you write to handle them handleSomething. The data goes down as props, and the events go up as function calls.
Props are read-only
A component must treat its props as read-only: it must never change them. The props belong to the parent, and a component that edited its input would make the screen impossible to reason about. React enforces this in development by freezing the props object:
function Hello(props) {
props.name = "Someone else"; // not allowed: props are read-only
return <p>Hello, {props.name}</p>;
}
function App() {
return <Hello name="Asha" />;
}
export default App;CONSOLE
warn: An error occurred in the <Hello> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'The error says that you cannot assign to the read-only property name. The same applies to changing something inside a prop, such as props.items.push(x), although that one is not caught for you. What do you do when you need a different value? Compute a new one from the prop (const shout = name.toUpperCase();), or use state if the value must change over time. The parent will pass fresh props whenever its own data changes, and your component simply runs again.
Where do props come from?
Props flow in one direction, down the tree, from parent to child. If a deeply nested component needs data from the top of the app, passing it through every level is called prop drilling. It is fine for a few levels; for more, the lesson on context shows an alternative. A component can also receive other components as props. That idea is what children is, the subject of the next lesson.
Checking props
React 19 no longer checks the older propTypes declarations for function components, so they have no effect. To describe what props a component expects, use TypeScript (a good next step after this course), or document the shape in a comment. Missing or wrongly typed props show up as undefined values and errors, so look at the Console when something looks empty.
Common mistakes
- Passing numbers or booleans as strings:
price="10"instead ofprice={10}. - Forgetting the double braces for an object prop:
style={{ color: "red" }}. - Mutating a prop (
props.items.push(x)). Copy first. - Writing
function Card(name, price)instead offunction Card({ name, price }). The first parameter is always the whole props object. - Expecting defaults to apply to
null. - Passing a new value for every render when a stable one would do. (Not a problem yet; the memoization lesson explains it.)
- Putting too many props on one component (more than five or six is a signal to restructure).
Exercise
- Add an
inStockdefault oftruetoProductCardand render one card without the prop. - Create a
Temperaturecomponent with propscelsiusandunit(default"C") that showsTemperature: 21°C. - Make a
Buttoncomponent that receivesonClick,children, and...rest, and forwardsrestto the<button>element, then use it withdisabled. - Pass
price="100"(a string) toTotaland see what thetypeofprints. Fix it.
Key points
- Props are the inputs of a component: you write them like HTML attributes, and the component receives them as one object.
- Use curly braces for anything that is not a plain string: numbers, booleans, arrays, objects and functions.
- Destructure props in the parameter list and give defaults with = in the same place.
- Props are read-only. A component must never change its own props; to change what is shown, the parent passes new props or the component uses state.