React Props
Overview
Props (short for properties) is the mechanism for passing data between React components. Props make components reusable and flexible, and are the foundation for building dynamic React applications. This chapter will deeply learn how to use Props, validation, and best practices.
🔄 Props Basics
What is Props
jsx
// Props is data passed from parent component to child component
function Greeting({ name, age }) {
return (
<div>
<h2>Hello, {name}!</h2>
<p>You are {age} years old</p>
</div>
);
}
function App() {
return (
<div>
<Greeting name="John" age={25} />
<Greeting name="Jane" age={30} />
</div>
);
}Props Characteristics
- Read-only: Components cannot modify their own props
- Unidirectional data flow: Data flows from parent to child
- Dynamic: Props can be variables, expressions, or functions
📦 Props Types
Basic Data Types
jsx
function UserCard({
name, // string
age, // number
isActive, // boolean
hobbies, // array
profile, // object
onEdit // function
}) {
return (
<div className={`user-card ${isActive ? 'active' : 'inactive'}`}>
<h3>{name}</h3>
<p>Age: {age}</p>
<p>Status: {isActive ? 'Online' : 'Offline'}</p>
<div className="hobbies">
<h4>Hobbies:</h4>
<ul>
{hobbies.map((hobby, index) => (
<li key={index}>{hobby}</li>
))}
</ul>
</div>
<p>Email: {profile.email}</p>
<button onClick={() => onEdit(profile.id)}>
Edit User
</button>
</div>
);
}
// Using the component
function App() {
const user = {
id: 1,
email: 'john@example.com',
address: 'New York'
};
const handleEdit = (userId) => {
console.log(`Edit user ID: ${userId}`);
};
return (
<UserCard
name="John"
age={28}
isActive={true}
hobbies={['Reading', 'Swimming', 'Coding']}
profile={user}
onEdit={handleEdit}
/>
);
}Special Props
children Property
jsx
// children is a special prop representing the component's children
function Card({ title, children }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="card-content">
{children}
</div>
</div>
);
}
function App() {
return (
<div>
<Card title="User Info">
<p>Name: John</p>
<p>Email: john@example.com</p>
<button>Edit</button>
</Card>
<Card title="Product List">
<ul>
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</ul>
</Card>
</div>
);
}Render Props Pattern
jsx
function DataProvider({ render, url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Using render props
function App() {
return (
<DataProvider
url="/api/users"
render={({ data, loading }) => (
<div>
{loading ? (
<p>Loading...</p>
) : (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)}
/>
);
}⚙️ Props Default Values
Using Default Parameters
jsx
function Button({
children,
type = 'button',
variant = 'primary',
size = 'medium',
disabled = false,
onClick = () => {}
}) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button
type={type}
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// Using default values
function App() {
return (
<div>
<Button>Default Button</Button>
<Button variant="secondary" size="large">
Secondary Button
</Button>
<Button variant="danger" disabled>
Disabled Button
</Button>
</div>
);
}Using defaultProps (Class Components)
jsx
class Welcome extends Component {
static defaultProps = {
name: 'Guest',
showTime: false
};
render() {
const { name, showTime } = this.props;
return (
<div>
<h1>Welcome, {name}!</h1>
{showTime && (
<p>Current time: {new Date().toLocaleString()}</p>
)}
</div>
);
}
}🔍 Props Destructuring and Passing
Props Destructuring
jsx
// Basic destructuring
function UserInfo({ user: { name, email, age } }) {
return (
<div>
<h2>{name}</h2>
<p>Email: {email}</p>
<p>Age: {age}</p>
</div>
);
}
// Renaming destructuring
function ProductCard({
product: {
name: productName,
price: productPrice
},
currency = '$'
}) {
return (
<div>
<h3>{productName}</h3>
<p>Price: {currency}{productPrice}</p>
</div>
);
}
// Rest parameters
function Input({ label, error, ...inputProps }) {
return (
<div className="form-group">
<label>{label}</label>
<input {...inputProps} />
{error && <span className="error">{error}</span>}
</div>
);
}
// Usage example
function LoginForm() {
return (
<form>
<Input
label="Username"
type="text"
placeholder="Enter username"
required
/>
<Input
label="Password"
type="password"
placeholder="Enter password"
error="Password must be at least 6 characters"
required
/>
</form>
);
}Props Passing Patterns
jsx
// Pass through props
function FormInput(props) {
return <input className="form-input" {...props} />;
}
// Selective passing
function CustomButton({ variant, size, children, ...buttonProps }) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button className={className} {...buttonProps}>
{children}
</button>
);
}
// Composition passing
function IconButton({ icon, children, ...props }) {
return (
<CustomButton {...props}>
<span className="icon">{icon}</span>
{children}
</CustomButton>
);
}🛡️ Props Validation
PropTypes (Development)
jsx
import PropTypes from 'prop-types';
function UserProfile({
user,
showEmail,
onEdit,
theme
}) {
return (
<div className={`user-profile theme-${theme}`}>
<h2>{user.name}</h2>
<p>Age: {user.age}</p>
{showEmail && <p>Email: {user.email}</p>}
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
UserProfile.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string
}).isRequired,
showEmail: PropTypes.bool,
onEdit: PropTypes.func.isRequired,
theme: PropTypes.oneOf(['light', 'dark'])
};
UserProfile.defaultProps = {
showEmail: false,
theme: 'light'
};TypeScript Type Definitions
tsx
interface User {
id: number;
name: string;
age: number;
email?: string;
}
interface UserProfileProps {
user: User;
showEmail?: boolean;
onEdit: (userId: number) => void;
theme?: 'light' | 'dark';
}
function UserProfile({
user,
showEmail = false,
onEdit,
theme = 'light'
}: UserProfileProps) {
return (
<div className={`user-profile theme-${theme}`}>
<h2>{user.name}</h2>
<p>Age: {user.age}</p>
{showEmail && user.email && <p>Email: {user.email}</p>}
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}🎯 Props Best Practices
1. Keep Props Interface Simple
jsx
// ❌ Too many props
function ComplexComponent({
title, subtitle, content, imageUrl, imageAlt,
showImage, showSubtitle, titleColor, contentColor,
onClick, onHover, onFocus, className, style
}) {
// Complex component implementation
}
// ✅ Use objects to organize related props
function BetterComponent({
content: { title, subtitle, body },
image: { url, alt, show = true },
styling: { titleColor, contentColor, className, style },
handlers: { onClick, onHover, onFocus }
}) {
// Clearer component implementation
}2. Use Callback Functions Reasonably
jsx
function TodoItem({ todo, onUpdate }) {
const handleToggle = () => {
onUpdate(todo.id, { completed: !todo.completed });
};
const handleEdit = (newText) => {
onUpdate(todo.id, { text: newText });
};
return (
<div className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={handleToggle}
/>
<EditableText
text={todo.text}
onSave={handleEdit}
/>
</div>
);
}3. Avoid Props Mutation
jsx
// ❌ Wrong: Modifying props
function BadComponent({ items }) {
items.push({ id: Date.now(), text: 'new item' }); // Don't do this
return <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>;
}
// ✅ Correct: Create new array
function GoodComponent({ items, onAddItem }) {
const handleAdd = () => {
const newItem = { id: Date.now(), text: 'new item' };
onAddItem(newItem);
};
return (
<div>
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button onClick={handleAdd}>Add Item</button>
</div>
);
}📊 Performance Considerations
Using memo for Optimization
jsx
import { memo } from 'react';
const ExpensiveItem = memo(function Item({ item, onSelect }) {
console.log(`Rendering item: ${item.id}`);
return (
<div onClick={() => onSelect(item.id)}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
// Custom comparison function
const OptimizedItem = memo(function Item({ item, selected }) {
return (
<div className={selected ? 'selected' : ''}>
<h3>{item.title}</h3>
</div>
);
}, (prevProps, nextProps) => {
return prevProps.item.id === nextProps.item.id &&
prevProps.selected === nextProps.selected;
});📝 Chapter Summary
Props is the core mechanism for React component communication. Mastering Props usage is essential for building maintainable React applications.
Key Takeaways
- ✅ Props is read-only, used for parent-child component communication
- ✅ Supports various data types and function passing
- ✅ children is a special prop
- ✅ Can set default values and perform type validation
- ✅ Reasonable props design improves component usability
Best Practices
- Keep Props interface simple and clear
- Use TypeScript or PropTypes for type checking
- Avoid excessive props passing
- Use default values reasonably
- Consider performance impact, use memo appropriately
Continue Learning: Next Chapter - React State