React Props Explained — Passing Data Between Components
In this tutorial, you will learn about React Props Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
React props are the mechanism for passing data from a parent component to a child component, enabling reusable and configurable components throughout your application.
What You'll Learn
- How props work and why they are read-only
- How to destructure props for cleaner code
- How to set default values for props
- How to use the children prop for composition
- How to validate props with PropTypes
Why It Matters
Props are the primary way components communicate in React. Without props, every component would be static and isolated. Props make components configurable, reusable, and connected to their parent's data.
Real-World Use
Durga Antivirus Pro's ScanResultCard accepts props for threat level, file name, scan date, and action handlers. The same component renders differently based on the data passed, without knowing where the data comes from.
flowchart LR
A[Parent Component] -->|props: name, email, onSelect| B[Child Component]
B -.->|reads props| C[Renders UI]
A -.->|can pass callbacks| B
B -.->|calls callback| A
style A fill:#3b82f6,color:#fff
Basic Props
Props are passed like HTML attributes:
function WelcomeBanner({ title, subtitle, theme }) {
const styles = {
light: { background: "#f0f9ff", color: "#1e40af" },
dark: { background: "#1e3a5f", color: "#93c5fd" },
};
return (
<div style={{ padding: "20px", borderRadius: "8px", ...styles[theme || "light"] }}>
<h1>{title}</h1>
{subtitle && <p>{subtitle}</p>}
</div>
);
}
// Usage
<WelcomeBanner
title="Welcome to the Dashboard"
subtitle="You have 3 new notifications"
theme="dark"
/>
<WelcomeBanner
title="Quick Start Guide"
theme="light"
/>
Expected output: Two banners with different titles, optional subtitle, and theme-specific colors.
Props are the component's input parameters. They arrive as an object. Destructuring them in the function parameter makes the code cleaner and documents what props the component expects.
Props Are Read-Only
Never modify props directly:
function Counter({ initialCount }) {
// BAD: Never modify props
// initialCount = initialCount + 1;
// GOOD: Use local state
const [count, setCount] = useState(initialCount);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
function UserProfile({ user }) {
// BAD: Mutating prop
// user.name = "New Name";
// GOOD: Create a copy
const displayName = user.name.toUpperCase();
return <p>{displayName}</p>;
}
Expected output: The counter uses local state initialized from props, never modifying the prop directly.
React components must treat props as immutable. Mutating props causes unpredictable behavior and defeats React's change detection. If you need to modify data, lift state up or use a callback.
The Children Prop
children lets components wrap arbitrary content:
function Panel({ title, children, footer }) {
return (
<div className="panel">
<div className="panel-header">
<h2>{title}</h2>
</div>
<div className="panel-body">
{children}
</div>
{footer && (
<div className="panel-footer">
{footer}
</div>
)}
</div>
);
}
// Usage with children
<Panel
title="User Settings"
footer={<button>Save Changes</button>}
>
<div className="form-group">
<label>Name: <input /></label>
</div>
<div className="form-group">
<label>Email: <input /></label>
</div>
</Panel>
Expected output: A panel with header, body (containing form fields), and footer with a save button.
children is whatever JSX is placed between the component's opening and closing tags. It can be a string, element, array, or function. This pattern is essential for layout and wrapper components.
Passing Callbacks as Props
Functions as props enable child-to-parent communication:
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
{todo.title}
</span>
<button onClick={() => onDelete(todo.id)} className="delete-btn">
Delete
</button>
</li>
);
}
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, title: "Learn React", completed: false },
{ id: 2, title: "Build an app", completed: false },
]);
const handleToggle = (id) => {
setTodos(todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t));
};
const handleDelete = (id) => {
setTodos(todos.filter(t => t.id !== id));
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
);
}
Expected output: A todo list where clicking the checkbox toggles completion and clicking delete removes the item.
Passing callbacks as props follows the "data flows down, events flow up" pattern. The parent owns the state and provides functions for the child to call when events occur.
Default Props and PropTypes
Document and validate props with default values and PropTypes:
import PropTypes from "prop-types";
function ProgressBar({ value, max, color, size, showLabel }) {
const percentage = Math.min((value / max) * 100, 100);
const heights = { small: 8, medium: 16, large: 24 };
return (
<div style={{ width: "100%", background: "#e5e7eb", borderRadius: "8px", height: `${heights[size]}px` }}>
<div
style={{
width: `${percentage}%`,
background: color,
height: "100%",
borderRadius: "8px",
transition: "width 0.3s ease"
}}
/>
{showLabel && <span style={{ fontSize: "0.8rem" }}>{Math.round(percentage)}%</span>}
</div>
);
}
ProgressBar.defaultProps = {
max: 100,
color: "#3b82f6",
size: "medium",
showLabel: false,
};
ProgressBar.propTypes = {
value: PropTypes.number.isRequired,
max: PropTypes.number,
color: PropTypes.string,
size: PropTypes.oneOf(["small", "medium", "large"]),
showLabel: PropTypes.bool,
};
Expected output: A progress bar with configurable value, color, size, and label. Default values apply when props are omitted.
PropTypes warn in development when expected props are missing or have wrong types. Default props ensure the component works with minimal configuration.
Prop Patterns
Common patterns for flexible component APIs:
// Spread props pattern
function Input({ label, error, ...inputProps }) {
return (
<div className="input-group">
{label && <label>{label}</label>}
<input
{...inputProps}
className={`input ${error ? "input-error" : ""}`}
/>
{error && <span className="error-message">{error}</span>}
</div>
);
}
// Render prop pattern
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(d => { setData(d); setLoading(false); });
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <Spinner /> : <UserList users={data} />
)}
/>
Expected output: Flexible components that accept spread props for native attributes and render props for customizable rendering.
The spread pattern lets components pass unknown props directly to DOM elements. Render props invert control, letting the parent decide how to render.
Common Mistakes
Modifying props directly — Props are immutable. Mutating them causes bugs and React warnings.
Passing too many unrelated props — If a component receives 10+ props, it likely does too much. Group related props or split the component.
Prop drilling — Passing props through many intermediate components that do not use them. Use context or composition to avoid this.
Forgetting to destructure props —
props.nameeverywhere is verbose and unclear. Destructure at the function parameter.Not providing default values — Components crash when optional props are missing. Always provide sensible defaults or use PropTypes.
Practice Questions
How does data flow between React components? Data flows down through props from parent to child. Events flow up through callback props.
What happens if you try to modify a prop? React warns in development. The prop does not change. Mutations cause unpredictable behavior.
What is the children prop? A special prop that represents the JSX content between the component's opening and closing tags.
How do you pass a function as a prop? Pass it like any other prop:
<Child onEvent={handleEvent} />. The child calls it withprops.onEvent().What is prop drilling? Passing props through multiple intermediate components that do not use them, just to reach a deeper component.
Challenge
Build a ConfigurableTable component that accepts columns configuration (array of objects with key, label, render functions), data array, sorting callbacks, and row click handler. The component should be reusable for any data type by changing the columns prop.
FAQ
Mini Project
Build a FormField component that accepts label, error, helperText, and spreads remaining props to an <input>. Build a CheckoutForm using the component with fields for name, email, address, and credit card. Validate that required fields are not empty. Show error messages. Use prop spreading to pass native input attributes like type, placeholder, maxLength.
What's Next
Continue with state and effects:
React State, React Effects, React Hooks
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro