Props (short for "properties") are how you pass data from one component to another in React. They're like arguments to a function - they let you customize and reuse components with different data. In this lesson, you'll learn everything about props: how to pass them, receive them, use different types, set defaults, and understand the one-way data flow that makes React predictable and maintainable.
What Are Props?
Think of props like settings or parameters for your components. Just like you can customize a car with different colors, features, and options, you can customize React components with props.
Without props, every component would be identical:
function Greeting() {
return <h1>Hello, World!</h1>
}
// Always says "Hello, World!" - not very useful!
<Greeting />
<Greeting />
<Greeting />With props, the same component becomes flexible:
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>
}
// Now each greeting can be different!
<Greeting name="Alice" /> {/* Hello, Alice! */}
<Greeting name="Bob" /> {/* Hello, Bob! */}
<Greeting name="Charlie" /> {/* Hello, Charlie! */}Props are:
- Read-only - Components can't modify their own props
- Passed from parent to child - Data flows downward
- Objects - All props are bundled into one object
- Customizable - You can pass any data type
Passing Props
You pass props to a component the same way you add attributes to HTML elements:
Basic Prop Passing
function UserCard(props) {
return (
<div className="user-card">
<h2>{props.name}</h2>
<p>Age: {props.age}</p>
<p>Email: {props.email}</p>
</div>
)
}
// Pass props like HTML attributes
function App() {
return (
<div>
<UserCard
name="Alice Johnson"
age={25}
email="alice@example.com"
/>
<UserCard
name="Bob Smith"
age={30}
email="bob@example.com"
/>
</div>
)
}💡 String vs Expression Props
String props can use quotes: name="Alice"
Other types need curly braces: age={25}age="25" → String "25"age={25} → Number 25
Passing Different Data Types
function Demo(props) {
return (
<div>
<p>String: {props.text}</p>
<p>Number: {props.count}</p>
<p>Boolean: {String(props.isActive)}</p>
<p>Array: {props.items.join(', ')}</p>
<p>Object: {props.user.name}</p>
</div>
)
}
function App() {
const userData = { name: 'Alice', age: 25 }
const listItems = ['React', 'JavaScript', 'CSS']
return (
<Demo
text="Hello" // String
count={42} // Number
isActive={true} // Boolean
items={listItems} // Array
user={userData} // Object
onClick={() => alert('Hi')} // Function
/>
)
}Passing Expressions
function ProductCard(props) {
return (
<div>
<h3>{props.name}</h3>
<p>${props.price}</p>
</div>
)
}
function App() {
const basePrice = 100
const discount = 0.2
return (
<ProductCard
name="Laptop"
price={basePrice * (1 - discount)} // Expressions work!
// This passes 80
/>
)
}Receiving Props
Props are passed to your component as the first parameter. It's an object containing all the props:
Accessing Props
function Greeting(props) {
console.log(props) // { name: "Alice", age: 25 }
return (
<div>
<h1>Hello, {props.name}!</h1>
<p>You are {props.age} years old</p>
</div>
)
}
// Usage:
<Greeting name="Alice" age={25} />Using Multiple Props
function BlogPost(props) {
return (
<article>
<h1>{props.title}</h1>
<p className="meta">
By {props.author} on {props.date}
</p>
<div className="content">
{props.content}
</div>
<div className="tags">
{props.tags.map((tag, index) => (
<span key={index} className="tag">{tag}</span>
))}
</div>
</article>
)
}
// Usage:
<BlogPost
title="Understanding React Props"
author="Jane Doe"
date="2024-01-15"
content="Props are awesome..."
tags={['React', 'JavaScript', 'Tutorial']}
/>Different Prop Types in Action
String Props
function Title(props) {
return <h1>{props.text}</h1>
}
// Both work for strings
<Title text="Hello World" />
<Title text={'Hello World'} />Number Props
function Counter(props) {
return <div>Count: {props.count}</div>
}
<Counter count={42} />
<Counter count={0} />
<Counter count={-5} />
<Counter count={3.14} />Boolean Props
function StatusBadge(props) {
return (
<span className={props.isActive ? 'active' : 'inactive'}>
{props.isActive ? 'Online' : 'Offline'}
</span>
)
}
// These are equivalent:
<StatusBadge isActive={true} />
<StatusBadge isActive /> // Shorthand for true
// For false, you must be explicit:
<StatusBadge isActive={false} />Array Props
function TagList(props) {
return (
<div>
{props.tags.map((tag, index) => (
<span key={index} className="tag">{tag}</span>
))}
</div>
)
}
<TagList tags={['React', 'JavaScript', 'Web']} />Object Props
function UserProfile(props) {
return (
<div>
<h2>{props.user.name}</h2>
<p>Age: {props.user.age}</p>
<p>Email: {props.user.email}</p>
</div>
)
}
const userData = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
}
<UserProfile user={userData} />Function Props
function Button(props) {
return (
<button onClick={props.onClick}>
{props.label}
</button>
)
}
function App() {
const handleClick = () => {
alert('Button clicked!')
}
return (
<div>
<Button label="Click Me" onClick={handleClick} />
<Button label="Alert" onClick={() => alert('Hello!')} />
</div>
)
}JSX Props
function Card(props) {
return (
<div className="card">
<div className="card-header">
{props.header}
</div>
<div className="card-body">
{props.body}
</div>
</div>
)
}
<Card
header={<h1>Welcome</h1>}
body={<p>This is the content</p>}
/>Default Props
Sometimes you want props to have default values if they're not provided:
Method 1: Default Parameters
function Button({ label = 'Click Me', variant = 'primary' }) {
return (
<button className={`btn btn-${variant}`}>
{label}
</button>
)
}
// Uses defaults
<Button />
// Renders: <button class="btn btn-primary">Click Me</button>
// Overrides defaults
<Button label="Submit" variant="success" />
// Renders: <button class="btn btn-success">Submit</button>Method 2: Logical OR (||)
function Greeting(props) {
const name = props.name || 'Guest'
const greeting = props.greeting || 'Hello'
return <h1>{greeting}, {name}!</h1>
}
<Greeting />
// Renders: Hello, Guest!
<Greeting name="Alice" greeting="Welcome" />
// Renders: Welcome, Alice!Method 3: Nullish Coalescing (??)
function Counter(props) {
// ?? only defaults for null/undefined, not 0 or false
const count = props.count ?? 10
return <div>Count: {count}</div>
}
<Counter /> // Count: 10
<Counter count={0} /> // Count: 0 (not 10!)
<Counter count={5} /> // Count: 5
// Compare with ||:
const count = props.count || 10
<Counter count={0} /> // Would show 10, not 0!When to use which:
• Default parameters - Most common, cleanest
• || - When you want to treat 0, "", false as "not provided"
• ?? - When 0 or false are valid values you want to keep
Understanding Data Flow
Props flow in one direction: from parent to child. This is called "one-way data flow" or "unidirectional data flow":
Props Flow: Parent → Child
function App() {
const userName = "Alice"
const userAge = 25
// App passes props down to UserCard
return <UserCard name={userName} age={userAge} />
}
function UserCard(props) {
// UserCard receives props from App
// UserCard passes some props down to Avatar
return (
<div>
<Avatar name={props.name} />
<p>Age: {props.age}</p>
</div>
)
}
function Avatar(props) {
// Avatar receives props from UserCard
return <img src={`/avatars/${props.name}.jpg`} alt={props.name} />
}
// Data flows: App → UserCard → AvatarProps Are Read-Only!
You can never modify props inside a component:props.name = 'New Name' ❌ Never do this!
Props are immutable. If you need to change data, you'll use state (which we'll learn about in later lessons).
Spreading Props
You can pass all properties of an object as props using the spread operator:
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.age}</p>
<p>{props.email}</p>
</div>
)
}
function App() {
const user = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
}
// Instead of:
<UserCard name={user.name} age={user.age} email={user.email} />
// You can use spread:
<UserCard {...user} />
// Both are equivalent!
}Combining Spread with Additional Props
function Button(props) {
return <button {...props}>{props.children}</button>
}
function App() {
const buttonProps = {
className: 'btn',
disabled: false
}
return (
<div>
{/* Spread existing props and add more */}
<Button {...buttonProps} onClick={() => alert('Clicked!')}>
Click Me
</Button>
{/* Props defined later override spread props */}
<Button {...buttonProps} className="btn btn-primary">
Override Class
</Button>
</div>
)
}⚠️ Spread Order Matters
<Button {...props} className="new" />
→ "new" overrides props.className<Button className="new" {...props} />
→ props.className overrides "new"
Conditional Props
You can conditionally pass props based on conditions:
function Button({ isPrimary, isDisabled, onClick, children }) {
return (
<button
className={isPrimary ? 'btn-primary' : 'btn-secondary'}
disabled={isDisabled}
onClick={onClick}
>
{children}
</button>
)
}
function App() {
const isLoggedIn = true
const hasPermission = false
return (
<div>
{/* Pass props conditionally */}
<Button
isPrimary={isLoggedIn}
isDisabled={!hasPermission}
onClick={hasPermission ? handleClick : undefined}
>
{isLoggedIn ? 'Logout' : 'Login'}
</Button>
</div>
)
}Conditionally Including Props
function Link({ href, isExternal, children }) {
return (
href={href}
// Only include these props if isExternal is true
{...(isExternal && {
target: '_blank',
rel: 'noopener noreferrer'
})}
>
{children}
</a>
)
}
<Link href="/about">About</Link>
// Renders: <a href="/about">About</a>
<Link href="https://example.com" isExternal>
External
</Link>
// Renders: <a href="..." target="_blank" rel="noopener noreferrer">External</a>Common Props Mistakes
1. Trying to Modify Props
// ❌ NEVER modify props
function Counter(props) {
props.count = props.count + 1 // Error! Props are read-only
return <div>{props.count}</div>
}
// ✅ Props are read-only, use them as-is
function Counter(props) {
return <div>{props.count}</div>
}
// ✅ For changes, you'll use state (covered in later lessons)
function Counter() {
const [count, setCount] = React.useState(0)
return <div onClick={() => setCount(count + 1)}>{count}</div>
}2. Forgetting Curly Braces for Non-Strings
// ❌ Wrong - age will be a string "25", not number 25
<User age="25" isActive="true" />
// ✅ Correct - use curly braces for non-strings
<User age={25} isActive={true} />3. Passing Props to Wrong Component
function Parent() {
return (
<div>
<Child name="Alice" age={25} />
</div>
)
}
// ❌ Wrong - props go to the div, not Child
function Child() {
return (
<div name="Alice" age={25}> {/* These do nothing! */}
Content
</div>
)
}
// ✅ Correct - receive props in function parameter
function Child(props) {
return (
<div>
{props.name}, age {props.age}
</div>
)
}4. Expecting Props in Wrong Format
function UserCard(props) {
// ❌ Wrong - Expects props.user.name but receives props.name
return <h2>{props.user.name}</h2>
}
<UserCard name="Alice" age={25} />
// ✅ Match your expectations with what's passed
function UserCard(props) {
return <h2>{props.name}</h2>
}
// Or pass an object:
const user = { name: 'Alice', age: 25 }
<UserCard user={user} />
function UserCard(props) {
return <h2>{props.user.name}</h2>
}5. Using Props in Wrong Scope
// ❌ Wrong - props doesn't exist outside component
const greeting = props.name // Error!
function Greeting(props) {
return <h1>Hello, {props.name}</h1>
}
// ✅ Correct - use props inside component
function Greeting(props) {
const greeting = `Hello, ${props.name}`
return <h1>{greeting}</h1>
}Props Best Practices
1. Use Descriptive Prop Names
// ❌ Unclear
<Button x="Save" y={handleSave} z={true} />
// ✅ Clear and descriptive
<Button label="Save" onClick={handleSave} isPrimary={true} />2. Keep Related Props Together
// ❌ Hard to manage many related props
<User
firstName="Alice"
lastName="Johnson"
email="alice@example.com"
phone="123-456-7890"
street="123 Main St"
city="Boston"
state="MA"
zip="02101"
/>
// ✅ Group related props into objects
const user = {
name: { first: 'Alice', last: 'Johnson' },
contact: { email: 'alice@example.com', phone: '123-456-7890' },
address: { street: '123 Main St', city: 'Boston', state: 'MA', zip: '02101' }
}
<User data={user} />3. Validate Props (Optional)
function Button(props) {
// Basic validation
if (typeof props.onClick !== 'function') {
console.warn('Button: onClick prop should be a function')
}
if (!props.label) {
console.warn('Button: label prop is required')
}
return <button onClick={props.onClick}>{props.label}</button>
}
// For production, consider using TypeScript or PropTypes library4. Document Complex Props
/**
* UserCard component displays user information
*
* @param {Object} props
* @param {string} props.name - User's full name
* @param {number} props.age - User's age (must be positive)
* @param {string} props.email - User's email address
* @param {boolean} [props.isPremium=false] - Premium status (optional)
* @param {Function} props.onEdit - Callback when edit button clicked
*/
function UserCard(props) {
// Component implementation
}Key Takeaways
- Props are how you pass data from parent to child components
- Props are passed like HTML attributes:
<Component prop="value" /> - Props are received as the first parameter in your function
- Props are read-only - never modify them inside a component
- String props use quotes, other types use curly braces
- You can pass any data type: strings, numbers, booleans, arrays, objects, functions, JSX
- Use default parameters or
||/??for default values - Data flows one way: from parent to child (top to bottom)
- Use spread operator
{...props}to pass all properties at once - Boolean props can be shortened:
isActiveinstead ofisActive={true} - Props make components reusable with different data
What's Next?
You now know how to pass data between components using props! This makes your components flexible and reusable. But there's one special prop that deserves its own lesson: the children prop.
The children prop is unique - it lets you nest content inside components, just like you nest HTML elements. In the next lesson, you'll learn how to use children to create wrapper components, layouts, and build more flexible component architectures. Get ready to level up your component composition skills! 🎯