Understanding props is fundamental to React. Every component you build will receive and send props, making this knowledge essential for writing effective React code.
Props enable components to be reusable by allowing data to be passed in from parent components. A single component can render differently based on different props.
Basic Prop Passing
Simple Props Example
import { Greeting } from ‘./Greeting’;export function App() {
return (
<>
<Greeting name=“Alice” age={25} />
<Greeting name=“Bob” age={30} />
</>
);
}
// Child Component
export function Greeting(props) {
return <h1>Hello {props.name}, age {props.age}</h1>;
}
String vs Expression Props
<Button label=“Click Me” />// JavaScript expressions need braces
<Button count={5} />
<Button disabled={isLoading} />
<Button callback={() => console.log(‘clicked’)} />
<Button size={10 * 2} />
// Objects need double braces
<Button style={{ color: ‘red’, fontSize: ’16px’ }} />
Multiple Props
return (
<div style={{ border: ‘1px solid #ccc’, padding: ’20px’ }}>
<h2>{props.title}</h2>
<p>{props.description}</p>
<img src={props.imageUrl} alt={props.title} />
<button onClick={props.onAction}>
{props.buttonText}
</button>
</div>
);
}// Usage
<Card
title=“Product Name”
description=“Great product”
imageUrl=“https://…”
onAction={() => console.log(‘Action clicked’)}
buttonText=“Buy Now”
/>
Destructuring Patterns
Basic Destructuring
function Greeting(props) {
return <h1>Hello {props.name}</h1>;
}// With destructuring (cleaner)
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
// Multiple props
function UserProfile({ name, email, age, city }) {
return (
<div>
<h1>{name}</h1>
<p>Email: {email}</p>
<p>Age: {age}</p>
<p>City: {city}</p>
</div>
);
}
Renaming Properties
function Component({
className: customClass,
onClick: handleClick
}) {
return <button className={customClass} onClick={handleClick}>Click</button>;
}// Usage with renamed props
<Component className=“btn-primary” onClick={() => {}} />
Nested Destructuring
function UserCard({ user: { name, address: { city, country } } }) {
return (
<div>
<h2>{name}</h2>
<p>From {city}, {country}</p>
</div>
);
}// Usage
<UserCard user={{
name: ‘John’,
address: { city: ‘NYC’, country: ‘USA’ }
}} />
Array Destructuring in Props
function ColorBox({ colors: [primary, secondary] }) {
return (
<div>
<div style={{ backgroundColor: primary }}>Primary</div>
<div style={{ backgroundColor: secondary }}>Secondary</div>
</div>
);
}// Usage
<ColorBox colors={[‘red’, ‘blue’]} />
Default Values and Optional Props
Function Parameter Defaults
function Button({
text = ‘Click Me’,
disabled = false,
size = ‘medium’
}) {
return (
<button disabled={disabled} className={`btn-${size}`}>
{text}
</button>
);
}// These all work without errors
<Button />
<Button text=“Submit” />
<Button text=“Delete” disabled={true} />
Default Props Pattern
return (
<div className={`card card-priority-${priority}`}>
<img src={imageUrl} alt={title} />
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}// Set defaults after component definition
Card.defaultProps = {
priority: ‘normal’,
description: ‘No description provided’,
imageUrl: ‘/placeholder.png’
};
export default Card;
Optional Chaining for Safety
function UserInfo({ user }) {
return (
<div>
// Without optional chaining – can crash
<p>City: {user.address.city}</p>// With optional chaining – safe
<p>City: {user?.address?.city}</p>
// With fallback
<p>City: {user?.address?.city ?? ‘Unknown’}</p>
</div>
);
}
Working with Children Props
Children Basics
function Modal({ title, children }) {
return (
<div className=“modal”>
<h2>{title}</h2>
<div className=“modal-content”>
{children}
</div>
</div>
);
}// Usage – anything between tags becomes children
<Modal title=“Delete Confirmation”>
<p>Are you sure you want to delete?</p>
<button>Yes</button>
<button>No</button>
</Modal>
Children as Function (Render Props)
function DataFetcher({ url, children }) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);React.useEffect(() => {
fetch(url)
.then(r => r.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
// children is a function that receives data
return children(data, loading);
}
// Usage with function as child
<DataFetcher url=“/api/users”>
{(data, loading) => (
loading ? <p>Loading…</p> : <ul>{data.map(…)}</ul>
)}
</DataFetcher>
Multiple Named Slots
function Layout({ header, sidebar, children, footer }) {
return (
<div className=“layout”>
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
<footer>{footer}</footer>
</div>
);
}// Usage with named slots
<Layout
header={<h1>My Site</h1>}
sidebar={<Nav />}
footer={<p>© 2025</p>}
>
<p>Main content goes here</p>
</Layout>
Spread Operator Patterns
Forwarding Props
function CustomButton({ variant, …buttonProps }) {
return (
<button
className={`btn btn-${variant}`}
{…buttonProps}
>
{buttonProps.children}
</button>
);
}// All HTML attributes are automatically passed
<CustomButton
variant=“primary”
disabled={false}
onClick={() => {}}
type=“submit”
className=“my-custom-class”
>
Submit Form
</CustomButton>
Extracting and Spreading
function Card({
title,
description,
…containerProps
}) {
return (
<div {…containerProps}>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}// className, onClick, etc. are spread onto the div
<Card
title=“Title”
description=“Desc”
className=“card-primary”
onClick={() => {}}
id=“card-1”
/>
Merging Props Objects
const baseProps = {
className: ‘btn’,
disabled: false,
onClick: handleClick
};const additionalProps = {
className: ‘btn-primary’,
type: ‘submit’
};
// Merge: additionalProps overrides baseProps
const finalProps = { …baseProps, …additionalProps };
<button {…finalProps}>Submit</button>
Prop Validation with PropTypes
Basic PropTypes Setup
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
<p>Status: {isActive ? ‘Active’ : ‘Inactive’}</p>
</div>
);
}
// Define expected prop types
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
email: PropTypes.string,
isActive: PropTypes.bool
};
export default User;
Common PropTypes Validators
// Primitive types
string: PropTypes.string,
number: PropTypes.number,
boolean: PropTypes.bool,
function: PropTypes.func,
object: PropTypes.object,
array: PropTypes.array,
any: PropTypes.any,// Specific values (enum)
color: PropTypes.oneOf([‘red’, ‘green’, ‘blue’]),
// Multiple types
flexible: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
// Complex types
users: PropTypes.arrayOf(PropTypes.object),
settings: PropTypes.shape({
theme: PropTypes.string,
fontSize: PropTypes.number
}),
// Required
required: PropTypes.string.isRequired,
// Custom validation
customProp: (props, propName) => {
if (props[propName] < 0) {
return new Error(‘Value must be positive’);
}
}
};
TypeScript Props
Interface-Based Props
name: string;
age: number;
email?: string; // optional with ?
active: boolean;
}function UserProfile({ name, age, email, active }: UserProps) {
return (
<div>
<h1>{name}</h1>
<p>Age: {age}</p>
{email && <p>Email: {email}</p>}
<p>{active ? ‘Active’ : ‘Inactive’}</p>
</div>
);
}
Type-Based Props with Union Types
type ButtonSize = ‘small’ | ‘medium’ | ‘large’;interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
disabled?: boolean;
onClick: () => void;
children: React.ReactNode;
}
function Button({
variant = ‘primary’,
size = ‘medium’,
…props
}: ButtonProps) {
return (
<button className={`btn-${variant} btn-${size}`} {…props}>
{props.children}
</button>
);
}
Generic Props
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// Usage with type inference
const users: User[] = […];
<List
items={users}
renderItem={(user) => <>{user.name} ({user.age})</>}
keyExtractor={(user) => user.id}
/>
Props Best Practices
Naming Conventions
<Button onClick={handleSubmit} disabled={isLoading}>Submit</Button>
<UserCard user={userData} onUserUpdate={handleUserUpdate} />// ❌ Avoid: Vague or abbreviated names
<Button onClick={handle} dis={load}>Submit</Button>
<UserCard u={data} onU={update} />
// ✅ Event handlers: on[Event]
<Input onChange={handleChange} onBlur={handleBlur} onFocus={handleFocus} />
<Card onDelete={handleDelete} onEdit={handleEdit} />
Keep Components Simple
<ComplexComponent
title={title}
description={description}
image={image}
onClick={onClick}
onHover={onHover}
onFocus={onFocus}
theme={theme}
variant={variant}
size={size}
disabled={disabled}
loading={loading}
error={error}
success={success}
// … 10 more props
/>// ✅ Better: Split into smaller components
<Card title={title} image={image}>
<CardDescription text={description} />
<CardActions onClick={onClick} onHover={onHover} />
</Card>
Avoid Prop Drilling
<GrandParent user={user}>
<Parent user={user}>
<Child user={user}>
<GrandChild user={user} />
</Child>
</Parent>
</GrandParent>// ✅ Use Context API for deep nesting
<UserContext.Provider value={user}>
<GrandParent>
<Parent>
<Child>
<GrandChild /> {/* uses useContext(UserContext) */}
</Child>
</Parent>
</GrandParent>
</UserContext.Provider>
Document Your Props
* Button component for user interactions
* @param {string} variant – Button style (primary, secondary, danger)
* @param {string} size – Button size (small, medium, large)
* @param {boolean} disabled – Disable button
* @param {function} onClick – Click handler
* @param {ReactNode} children – Button content
*/
export function Button({
variant = ‘primary’,
size = ‘medium’,
disabled = false,
onClick,
children
}) {
// implementation
}