Props
Props - Passing Data to Components
Props (short for “properties”) are how you pass data from parent components to child components in React. They’re React’s way of making components reusable and dynamic.
What Are Props?
Props are arguments passed to components, just like function parameters:
// Parent passes data via props
function App() {
return <Greeting name="Alice" age={25} />;
}
// Child receives props as an object
function Greeting(props) {
return <h1>Hello, {props.name}! You are {props.age}.</h1>;
}
Destructuring Props
The modern way to handle props:
// Instead of this
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// Do this (destructure in parameter)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Multiple props
function UserCard({ name, email, age, avatar }) {
return (
<div>
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
<p>Age: {age}</p>
</div>
);
}
Passing Props
String props:
<Greeting name="Alice" />
Number props (use curly braces):
<Counter initialCount={0} />
Boolean props:
<Button disabled={true} />
<Button disabled /> {/* Shorthand for true */}
Array props:
<TodoList items={['Task 1', 'Task 2', 'Task 3']} />
Object props:
<UserCard user={{ name: 'Alice', age: 25 }} />
Function props:
<Button onClick={handleClick} />
Props are Read-Only
Never modify props - they’re immutable:
// ❌ Wrong - mutating props
function Button(props) {
props.text = "Modified"; // Error!
return <button>{props.text}</button>;
}
// ✅ Right - use props as-is
function Button({ text }) {
return <button>{text}</button>;
}
// ✅ Right - derive new values
function Button({ text }) {
const uppercase = text.toUpperCase();
return <button>{uppercase}</button>;
}
Default Props
Set default values for props:
// Method 1: Default parameters (modern)
function Greeting({ name = "Guest", greeting = "Hello" }) {
return <h1>{greeting}, {name}!</h1>;
}
// Method 2: defaultProps (legacy but still valid)
function Greeting({ name, greeting }) {
return <h1>{greeting}, {name}!</h1>;
}
Greeting.defaultProps = {
name: "Guest",
greeting: "Hello"
};
// Usage
<Greeting /> // Hello, Guest!
<Greeting name="Alice" /> // Hello, Alice!
Passing Multiple Props
Individual props:
<UserCard
name="Alice"
email="alice@example.com"
age={25}
avatar="/avatar.jpg"
/>
Spread operator (pass entire object):
const user = {
name: "Alice",
email: "alice@example.com",
age: 25,
avatar: "/avatar.jpg"
};
<UserCard {...user} /> // Spreads all properties as props
Combining spread with overrides:
<UserCard {...user} age={26} /> // Override specific prop
Children Prop
Special prop for content between component tags:
function Card({ children }) {
return (
<div className="card">
<div className="card-content">
{children}
</div>
</div>
);
}
// Usage
<Card>
<h3>Title</h3>
<p>This content becomes the children prop</p>
</Card>
Children can be anything:
// Text
<Button>Click me</Button>
// Elements
<Card>
<h3>Header</h3>
<p>Body</p>
</Card>
// Components
<Layout>
<Header />
<Content />
<Footer />
</Layout>
// Expressions
<Alert>
{error ? error.message : "No errors"}
</Alert>
Props Flow (One-Way Data Flow)
Data flows down from parent to child:
function App() {
const user = { name: "Alice", role: "Admin" };
return (
<div>
<Header user={user} /> {/* Parent → Child */}
<Sidebar user={user} /> {/* Parent → Child */}
<Content user={user} /> {/* Parent → Child */}
</div>
);
}
function Header({ user }) {
return <h1>Welcome, {user.name}</h1>;
}
Child cannot directly modify parent’s data. Use callback props instead (covered later).
Callback Props
Pass functions as props to let children communicate with parents:
function Parent() {
const handleChildClick = (message) => {
console.log("Child says:", message);
};
return <Child onAction={handleChildClick} />;
}
function Child({ onAction }) {
return (
<button onClick={() => onAction("Hello from child!")}>
Click me
</button>
);
}
Prop Types Validation
Using TypeScript (recommended):
interface UserCardProps {
name: string;
age: number;
email?: string; // Optional
}
function UserCard({ name, age, email }: UserCardProps) {
return (
<div>
<h3>{name}</h3>
<p>Age: {age}</p>
{email && <p>Email: {email}</p>}
</div>
);
}
Using PropTypes (JavaScript):
import PropTypes from 'prop-types';
function UserCard({ name, age, email }) {
return (
<div>
<h3>{name}</h3>
<p>Age: {age}</p>
{email && <p>Email: {email}</p>}
</div>
);
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string
};
Practical Examples
Example 1: Product Card
function ProductCard({ name, price, image, inStock, onAddToCart }) {
return (
<div className="product-card">
<img src={image} alt={name} />
<h3>{name}</h3>
<p className="price">${price.toFixed(2)}</p>
{inStock ? (
<button onClick={() => onAddToCart(name)}>
Add to Cart
</button>
) : (
<p className="out-of-stock">Out of Stock</p>
)}
</div>
);
}
// Usage
<ProductCard
name="Laptop"
price={999.99}
image="/laptop.jpg"
inStock={true}
onAddToCart={(productName) => console.log("Added:", productName)}
/>
Example 2: Reusable Button
function Button({
children,
variant = "primary",
size = "medium",
disabled = false,
onClick
}) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// Usage
<Button variant="primary" size="large" onClick={handleSave}>
Save Changes
</Button>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="danger" disabled>
Delete (disabled)
</Button>
Example 3: User Profile
function UserProfile({ user, showEmail = true, showBio = true }) {
return (
<div className="user-profile">
<Avatar src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
{showEmail && user.email && (
<p className="email">{user.email}</p>
)}
{showBio && user.bio && (
<p className="bio">{user.bio}</p>
)}
<Stats
followers={user.followers}
following={user.following}
/>
</div>
);
}
function Avatar({ src, alt }) {
return <img src={src} alt={alt} className="avatar" />;
}
function Stats({ followers, following }) {
return (
<div className="stats">
<span>{followers} Followers</span>
<span>{following} Following</span>
</div>
);
}
Example 4: List with Custom Rendering
function List({ items, renderItem }) {
return (
<ul className="list">
{items.map((item, index) => (
<li key={item.id || index}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// Usage with different render functions
const users = [
{ id: 1, name: "Alice", role: "Admin" },
{ id: 2, name: "Bob", role: "User" }
];
// Simple rendering
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>
// Complex rendering
<List
items={users}
renderItem={(user) => (
<div>
<strong>{user.name}</strong>
<span className="role">{user.role}</span>
</div>
)}
/>
Common Patterns
1. Configuration object:
function DataTable({ config }) {
const { columns, data, pageSize, sortable } = config;
return (
<table>
{/* Use config to render table */}
</table>
);
}
2. Render props:
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
// Usage
<MouseTracker
render={({ x, y }) => (
<p>Mouse at ({x}, {y})</p>
)}
/>
3. Props forwarding:
function FancyButton({ children, ...otherProps }) {
return (
<button className="fancy-button" {...otherProps}>
{children}
</button>
);
}
// All props except children are forwarded
<FancyButton onClick={handleClick} disabled>
Click me
</FancyButton>
Best Practices
1. Keep props simple:
// Good - simple props
<UserCard name="Alice" age={25} />
// Bad - overly nested
<Component data={{ user: { profile: { details: { name: "Alice" }}}}} />
2. Use destructuring:
// Good
function Component({ name, age, email }) { }
// Less good
function Component(props) {
return <div>{props.name} {props.age} {props.email}</div>;
}
3. Don’t pass too many props:
// Bad - too many props
<Component prop1={a} prop2={b} prop3={c} prop4={d} prop5={e} prop6={f} />
// Better - group related props
<Component user={userData} config={configData} handlers={handlers} />
// Or split into smaller components
<UserComponent user={userData} />
<ConfigComponent config={configData} />
4. Use meaningful names:
// Good
<Button onClick={handleSubmit} disabled={isLoading}>Submit</Button>
// Bad
<Button fn={fn1} flag={f}>Submit</Button>
5. Extract repeated prop sets:
// Before
<Button onClick={handleClick} className="primary" size="large">Save</Button>
<Button onClick={handleClick2} className="primary" size="large">Submit</Button>
// After
const buttonProps = {
className: "primary",
size: "large"
};
<Button {...buttonProps} onClick={handleClick}>Save</Button>
<Button {...buttonProps} onClick={handleClick2}>Submit</Button>
Common Mistakes
1. Modifying props:
// Wrong
function Component({ items }) {
items.push('new item'); // Mutating props!
return <ul>...</ul>;
}
// Right
function Component({ items }) {
const newItems = [...items, 'new item'];
return <ul>...</ul>;
}
2. Forgetting curly braces:
// Wrong - passes string "25" not number 25
<Component age="25" />
// Right
<Component age={25} />
3. Prop drilling (passing props through many levels):
// Problem
<GrandParent data={data} />
<Parent data={data} />
<Child data={data} />
<GrandChild data={data} /> // Finally used here
// Solution: Use Context or state management (covered later)
Props vs State
Props:
- Passed from parent
- Read-only
- Controlled by parent
- Use for configuration
State (covered next):
- Internal to component
- Mutable
- Controlled by component itself
- Use for interactivity
// Props example - data from parent
function Greeting({ name }) { // name is a prop
return <h1>Hello, {name}</h1>;
}
// State example - component's own data (next lesson)
function Counter() {
const [count, setCount] = useState(0); // count is state
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Next Article: State with useState