React Props - Complete Guide
Props (short for "properties") are React's way of passing data from parent components to child components. Think of them as arguments you pass to a function, but for React components. They're one of the fundamental concepts you need to master to build dynamic and reusable React applications.
What are Props?
Props are read-only data that flow from parent components down to child components. They make your components flexible and reusable - instead of hardcoding values inside a component, you pass them in as props, making the same component work with different data.
Imagine you have a Button component. Instead of creating separate components for "Submit", "Cancel", and "Delete" buttons, you can create one Button component and pass different text as props. That's the power of props!
Basic Props Example
Let's start with a simple example to see how props work:
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
function App() {
return <Greeting name="John" />;
}Here, we're passing the name prop from the App component to the Greeting component. The Greeting component receives it through the props parameter and uses it to display a personalized message.
Destructuring Props
Instead of accessing props through props.name, you can destructure them directly in the function parameters. This is cleaner and more readable:
function UserCard({ name, email, age }) {
return (
<div>
<h2>{name}</h2>
<p>Email: {email}</p>
<p>Age: {age}</p>
</div>
);
}
function App() {
return (
<UserCard
name="John Doe"
email="john@example.com"
age={25}
/>
);
}Destructuring makes it immediately clear what props a component expects, and it's the preferred way to write components in modern React.
Default Props
Sometimes you want props to have default values. This is useful when a prop is optional but you want to ensure it always has a value. There are two ways to set default props:
Using Default Parameters
The modern approach is to use default parameter values directly in the function signature:
function Button({ text, color = 'blue' }) {
return (
<button style={{ backgroundColor: color }}>
{text}
</button>
);
}
// Usage
<Button text="Click me" /> // color will be 'blue'
<Button text="Click me" color="red" /> // color will be 'red'This is clean and straightforward. If color isn't provided, it defaults to 'blue'.
Using defaultProps
You can also use the defaultProps property, though the default parameter approach is more common now:
function Button({ text, color }) {
return (
<button style={{ backgroundColor: color }}>
{text}
</button>
);
}
Button.defaultProps = {
color: 'blue',
text: 'Click me'
};Both approaches work, but default parameters are generally preferred because they're more concise and easier to read.
The Children Prop
The children prop is special in React. It represents the content placed between the opening and closing tags of a component:
function Card({ children, title }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">
{children}
</div>
</div>
);
}
function App() {
return (
<Card title="My Card">
<p>This is the card content</p>
<button>Click me</button>
</Card>
);
}Everything between <Card> and </Card> becomes the children prop. This is incredibly useful for creating wrapper components like modals, cards, or layout components.
Different Types of Props
Props can be of any JavaScript type. Here are the most common ones:
Strings
<Component name="John" />Numbers
<Component age={25} count={10} />Notice the curly braces for numbers - they're required because JSX treats everything in quotes as a string.
Booleans
<Component isActive={true} showDetails={false} />You can also pass booleans without the value when it's true:
<Component isActive /> // same as isActive={true}Arrays
<Component items={[1, 2, 3]} users={['John', 'Jane']} />Objects
<Component user={{ name: 'John', age: 25 }} />Functions
Functions are commonly passed as props, especially for event handlers:
function App() {
const handleClick = () => {
console.log('Button clicked!');
};
return <Button onClick={handleClick} />;
}Props Validation
Validating props helps catch errors early and makes your components more self-documenting. There are two main approaches:
Using TypeScript
TypeScript provides compile-time type checking:
interface ButtonProps {
text: string;
onClick: () => void;
disabled?: boolean;
}
function Button({ text, onClick, disabled = false }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{text}
</button>
);
}TypeScript will warn you if you use the component incorrectly, and many IDEs will provide autocomplete for props.
Using PropTypes
If you're not using TypeScript, PropTypes is a great alternative:
import PropTypes from 'prop-types';
function Button({ text, onClick, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{text}
</button>
);
}
Button.propTypes = {
text: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};PropTypes will show warnings in the console if props don't match the expected types, which is helpful during development.
Using Spread Operator with Props
The spread operator (...) is useful when you want to pass multiple props or forward props to child elements:
function UserCard({ name, email, ...otherProps }) {
return (
<div {...otherProps}>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// Usage
<UserCard
name="John"
email="john@example.com"
className="card"
id="user-1"
/>Here, className and id are collected into otherProps and spread onto the div. This is useful for creating flexible wrapper components.
Key takeaway: Props are for data that comes from outside the component, while state is for data that the component manages internally.
Best Practices
Here are some tips to help you use props effectively:
Always Destructure Props
Destructuring makes your code cleaner and more readable:
// Good - Clear and concise
function Component({ name, age }) { }
// Avoid - More verbose
function Component(props) {
const name = props.name;
const age = props.age;
}Use Meaningful Prop Names
Choose names that clearly describe what the prop represents:
// Good - Self-documenting
<UserCard userName="John" userEmail="john@example.com" />
// Avoid - Unclear
<UserCard a="John" b="john@example.com" />Provide Default Values
Default values make your components more flexible and prevent errors:
function Button({ text = 'Click me', variant = 'primary' }) {
// Component can work even if props aren't provided
}Keep Props Simple
Avoid passing too many props to a single component. If you find yourself passing many props, consider:
- Breaking the component into smaller pieces
- Grouping related props into an object
- Using composition instead
Don't Mutate Props
Props should be treated as read-only. Never modify props directly:
// Wrong - Don't do this!
function Component({ user }) {
user.name = 'New Name'; // Never mutate props!
}
// Right - Create a new object if needed
function Component({ user }) {
const updatedUser = { ...user, name: 'New Name' };
}Common Patterns
Conditional Props
You can conditionally pass props:
function Button({ text, variant, disabled }) {
return (
<button
className={`btn btn-${variant}`}
disabled={disabled}
>
{text}
</button>
);
}
// Conditionally pass disabled prop
<Button
text="Submit"
variant="primary"
{...(isLoading && { disabled: true })}
/>Prop Drilling
Sometimes you need to pass props through multiple levels of components. This is called "prop drilling":
function App() {
const user = { name: 'John', role: 'admin' };
return <Layout user={user} />;
}
function Layout({ user }) {
return <Header user={user} />;
}
function Header({ user }) {
return <UserMenu user={user} />;
}For deeply nested prop drilling, consider using Context API or state management libraries.
Conclusion
Props are essential to React development. They enable component reusability, make your code more maintainable, and create a clear data flow in your application. By mastering props, you'll be able to build flexible, reusable components that can adapt to different situations.
Remember, props flow downward from parent to child, they're read-only, and they're React's primary way of making components configurable and reusable.
Visual Explanation: Props Flow
Here's how props flow from parent to child:
Parent Component
┌─────────────────────────────┐
│ const user = { │
│ name: "John", │
│ age: 25 │
│ } │
│ │
│ <UserCard │
│ name={user.name} ────────┼───┐
│ age={user.age} ────────┼───┼───┐
│ /> │ │ │
└─────────────────────────────┘ │ │
│ │
▼ ▼
Child Component
┌─────────────────┐
│ function UserCard│
│ ({ name, age }) │
│ │
│ name = "John" │
│ age = 25 │
│ │
│ <h1>{name}</h1> │
│ <p>{age}</p> │
└─────────────────┘Key Points:
- Props flow DOWN (parent → child)
- Props are READ-ONLY (child can't change them)
- Props are like function arguments
Props vs State Comparison
┌─────────────────┬─────────────────┐
│ PROPS │ STATE │
├─────────────────┼─────────────────┤
│ From parent │ Internal │
│ Read-only │ Can change │
│ Immutable │ Mutable │
│ Passed down │ Managed locally │
│ Like arguments │ Like variables │
└─────────────────┴─────────────────┘Frequently Asked Questions (FAQ)
Q1: Can I change props in a child component?
A: No! Props are read-only. If you need to change data, lift the state up to the parent component and pass down a function to update it.
Q2: What happens if I don't pass a required prop?
A: The prop will be undefined. Use default values or PropTypes/TypeScript to handle this:
function Component({ name = 'Guest' }) {
return <div>Hello {name}</div>;
}Q3: Can I pass functions as props?
A: Yes! Functions are commonly passed as props, especially for event handlers:
function Parent() {
const handleClick = () => console.log('Clicked');
return <Child onClick={handleClick} />;
}Q4: How many props is too many?
A: If you're passing more than 5-7 props, consider:
- Grouping related props into an object
- Breaking the component into smaller pieces
- Using Context API for deeply nested props
Q5: Can I pass components as props?
A: Yes! This is called "render props" pattern:
<Container render={(data) => <Display data={data} />} />Q6: What's the difference between props and state?
A:
- Props - Data from parent, read-only, can't change
- State - Data managed by component, can change, triggers re-renders
Q7: Can I use props in the dependency array of useEffect?
A: Yes! If your effect uses props, include them:
useEffect(() => {
fetchUser(userId); // userId is a prop
}, [userId]); // Include prop in dependenciesQ8: How do I pass props to multiple levels?
A: You can pass through intermediate components (prop drilling), or use Context API for deeply nested data. For 2-3 levels, prop drilling is fine. For more, consider Context.
Q9: Can props be optional?
A: Yes! Use default parameters:
function Component({ name, age = 0 }) {
// age is optional, defaults to 0
}Q10: What happens to props when parent re-renders?
A: Child components re-render when props change. React compares old and new props - if they're different, the child re-renders. If props are the same, React skips re-rendering (with React.memo).
Next Steps
Now that you understand props, here's what to explore next:
- Next: Learn about State Management in React - Discover how to manage dynamic data within components
- Understand useState Hook - Learn the hook for managing state
- Explore State vs Props - Understand when to use each one
- Study Event Handling - See how function props enable user interactions