Props
Props in React
Props (short for properties) are how components communicate with each other in React. They are read-only data passed from parent components to child components.
What are Props?
Props are arguments passed into React components, similar to function parameters. They allow you to pass data and event handlers down the component tree.
// Parent component passes props
<Greeting name="Alice" age={25} />
// Child component receives props
function Greeting(props) {
return <h1>Hello, {props.name}! You are {props.age} years old.</h1>;
}
Passing Props
Basic Props
function App() {
return (
<div>
<UserCard
name="John Doe"
email="[email protected]"
isActive={true}
age={30}
/>
</div>
);
}
function UserCard(props) {
return (
<div className="user-card">
<h2>{props.name}</h2>
<p>Email: {props.email}</p>
<p>Age: {props.age}</p>
<p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}
Different Types of Props
function PropsExample() {
const user = { name: 'Alice', id: 1 };
const hobbies = ['reading', 'coding', 'gaming'];
return (
<Profile
// String prop
name="John"
// Number prop
age={25}
// Boolean prop
isVerified={true}
// Object prop
user={user}
// Array prop
hobbies={hobbies}
// Function prop
onClick={() => console.log('Clicked!')}
// JSX prop
icon={<span>👤</span>}
/>
);
}
Destructuring Props
Make your code cleaner by destructuring props:
// Without destructuring
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// With destructuring in function parameter
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// With destructuring in function body
function Greeting(props) {
const { name, age, city } = props;
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}, City: {city}</p>
</div>
);
}
Default Props
Set default values for props:
// Using default parameters
function Button({ text = 'Click me', color = 'blue', size = 'medium' }) {
return (
<button className={`btn btn-${color} btn-${size}`}>
{text}
</button>
);
}
// Using defaultProps (older approach)
function Button({ text, color, size }) {
return (
<button className={`btn btn-${color} btn-${size}`}>
{text}
</button>
);
}
Button.defaultProps = {
text: 'Click me',
color: 'blue',
size: 'medium'
};
Props.children
Access content passed between component tags:
function Card({ title, children }) {
return (
<div className="card">
<div className="card-header">
<h3>{title}</h3>
</div>
<div className="card-body">
{children}
</div>
</div>
);
}
// Usage
function App() {
return (
<Card title="User Profile">
<p>Name: John Doe</p>
<p>Email: [email protected]</p>
<button>Edit Profile</button>
</Card>
);
}
Spreading Props
Pass all props or specific props to child components:
function Button(props) {
return <button {...props} />;
}
// More controlled spreading
function CustomButton({ variant, size, ...otherProps }) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
{...otherProps}
/>
);
}
// Usage
<CustomButton
variant="primary"
size="large"
onClick={handleClick}
disabled={false}
type="submit"
/>
Props Validation
Use PropTypes to validate props (in development):
import PropTypes from 'prop-types';
function UserProfile({ name, age, email, isAdmin, onUpdate }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
{isAdmin && <p>Administrator</p>}
<button onClick={onUpdate}>Update</button>
</div>
);
}
UserProfile.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string.isRequired,
isAdmin: PropTypes.bool,
onUpdate: PropTypes.func.isRequired
};
// More PropTypes examples
MyComponent.propTypes = {
// Basic types
optionalString: PropTypes.string,
optionalNumber: PropTypes.number,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalObject: PropTypes.object,
optionalArray: PropTypes.array,
// Specific shapes
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired
}),
// Array of specific type
items: PropTypes.arrayOf(PropTypes.string),
// One of specific values
status: PropTypes.oneOf(['pending', 'approved', 'rejected']),
// One of many types
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
// Custom validator
customProp: function(props, propName, componentName) {
if (!/^[0-9]+$/.test(props[propName])) {
return new Error('Invalid prop');
}
}
};
Passing Functions as Props
Handle events in parent components:
function TodoApp() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
return (
<div>
<TodoForm onAddTodo={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} />
</div>
);
}
function TodoForm({ onAddTodo }) {
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
onAddTodo(input);
setInput('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo..."
/>
<button type="submit">Add</button>
</form>
);
}
Props vs State
Understanding the difference:
function ParentComponent() {
const [count, setCount] = useState(0); // State - internal, mutable
return (
<ChildComponent
count={count} // Props - external, immutable
onIncrement={() => setCount(count + 1)}
/>
);
}
function ChildComponent({ count, onIncrement }) {
// Can't do this - props are read-only!
// count = 10; // ❌ Error
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
}
Conditional Props
Pass props conditionally:
function ConditionalProps() {
const isSpecial = true;
const additionalProps = isSpecial ? { special: true, badge: '⭐' } : {};
return (
<div>
<Item
name="Regular Item"
{...additionalProps}
/>
{/* Or inline */}
<Item
name="Another Item"
{...(isSpecial && { special: true, badge: '⭐' })}
/>
</div>
);
}
Common Patterns
Render Props
function DataFetcher({ render, url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <p>Loading...</p> : <UserList users={data} />
)}
/>
Prop Drilling Problem
// Problem: Passing props through multiple levels
function App() {
const [user, setUser] = useState({ name: 'John', theme: 'dark' });
return <Dashboard user={user} />;
}
function Dashboard({ user }) {
return <Header user={user} />;
}
function Header({ user }) {
return <UserMenu user={user} />;
}
function UserMenu({ user }) {
return <span>Welcome, {user.name}!</span>;
}
// Solution: Use Context API or state management
Best Practices
- Keep props minimal - Only pass what's needed
- Use descriptive names -
isLoading
better thanloading
- Validate props - Use PropTypes or TypeScript
- Avoid passing entire objects when only few properties are needed
- Document complex props with comments
- Use default values for optional props
- Prefer composition over prop drilling
Common Mistakes
Mutating Props
// ❌ Never mutate props
function Bad({ user }) {
user.name = 'New Name'; // Don't do this!
return <div>{user.name}</div>;
}
// ✅ Create new object
function Good({ user }) {
const updatedUser = { ...user, name: 'New Name' };
return <div>{updatedUser.name}</div>;
}
Using Index as Key
// ❌ Avoid using index as key for dynamic lists
items.map((item, index) => <Item key={index} {...item} />)
// ✅ Use stable, unique ID
items.map(item => <Item key={item.id} {...item} />)
Summary
Props are fundamental to React's component architecture. They enable:
- Component reusability
- Data flow from parent to child
- Component composition
- Event handling delegation
- Separation of concerns
Master props and you'll be able to build complex, maintainable React applications!