React Props: How to Pass Data Between Components
Props are the primary way to pass data from a parent component down to child components in React, enabling one-way data flow. Understanding how to use props is essential for building dynamic, reusable components. This article shows you how to pass and receive props, handle complex data structures, and apply best practices to avoid common pitfalls like over-drilling.
Key Takeaways
- Props are how parent components communicate with children — they pass data down via component attributes, making components dynamic and reusable.
- One-way data flow — data flows downward only (parent to child); children cannot modify props directly.
- Props are read-only — mutating props breaks React's predictability; use state to manage data that changes.
- Destructuring and prop drilling — destructure props in function signatures for cleaner code, but avoid passing props through many levels of components that don't use them.
- Key prop for lists — always provide a unique
keywhen rendering lists to help React identify changed, added, or removed items.
What Are Props and How Do They Work?
Props (short for properties) are the mechanism for passing data from a parent component to a child component. Think of props as function arguments — you pass them when you instantiate a component, and the component reads them to render its output.
A parent component owns the data and passes it down via attributes. Each child receives those props as an object and can use them to render dynamic content. This creates a predictable, one-directional data flow: parent controls the data, child only reads it.
How Do You Pass Props from a Parent Component?
To pass props, add attributes to your component tag in JSX, just like HTML attributes. The parent component defines the data and passes it down.
import React from 'react';
import UserCard from './UserCard';
const App = () => {
const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
title: 'Inventor and Actress'
};
return (
<div>
<h1>User Profile</h1>
<UserCard userData={user} />
</div>
);
};
export default App;
In this example, the App component creates a user object and passes it to UserCard via the userData prop. The curly braces {user} tell JSX to evaluate the JavaScript variable; without them, React would treat user as a string literal.
Step-by-Step: What Happens in the Code Above
- Data lives in the parent —
Appholds theuserobject. - Prop name —
userDatais the name of the prop (you choose this name). - Prop value —
{user}is the actual data being passed (wrapped in braces because it is a JavaScript object). - Child receives it —
UserCardwill now have access to this data via itspropsparameter.
How Do You Receive and Use Props in a Child Component?
A child component receives props as a parameter to its function. You can either access props as an object (props.name) or destructure them in the function signature ({ name }) for cleaner code.
import React from 'react';
const Avatar = (props) => {
return (
<img
src={props.imageUrl}
alt={props.name}
width={100}
height={100}
/>
);
};
const UserInfo = (props) => {
return (
<div>
<h2>{props.name}</h2>
<p>{props.title}</p>
</div>
);
};
const UserCard = (props) => {
const { userData } = props;
return (
<div style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}>
<Avatar imageUrl={userData.imageUrl} name={userData.name} />
<UserInfo name={userData.name} title={userData.title} />
</div>
);
};
export default UserCard;
The UserCard component receives the entire userData object, extracts the properties it needs, and passes individual pieces to Avatar and UserInfo as separate props. This nested prop passing is called prop drilling — each level passes props down to the next.
Destructuring Props for Cleaner Code
Instead of writing props.name repeatedly, destructure props in the function signature:
const UserInfo = ({ name, title }) => {
return (
<div>
<h2>{name}</h2>
<p>{title}</p>
</div>
);
};
This is more concise and readable, especially when components receive many props.
How Do You Render Lists of Components with Props?
When rendering a list of components, pass each item's data as props and provide a unique key prop so React can efficiently track which items changed.
import React from 'react';
import UserCard from './components/UserCard';
const users = [
{ id: 1, name: 'Hedy Lamarr', imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg', title: 'Inventor and Actress' },
{ id: 2, name: 'Katherine Johnson', imageUrl: 'https://i.imgur.com/MK3eW3Am.jpg', title: 'Aeronautical Engineer' }
];
const App = () => {
return (
<div>
<h1>User Gallery</h1>
{users.map(user => (
<UserCard key={user.id} userData={user} />
))}
</div>
);
};
export default App;
Why the key prop matters: React uses the key to match each component with its data across re-renders. Without keys, React may re-render components unnecessarily or associate the wrong data with the wrong UI. Always use a stable, unique identifier (like id) as the key, not the array index.
What Are the Best Practices for Using Props?
Following best practices ensures your components remain maintainable and efficient.
Destructure Props in the Function Signature
Destructuring reduces visual noise and makes it clear which props a component expects:
const UserInfo = ({ name, title }) => (
<div>
<h2>{name}</h2>
<p>{title}</p>
</div>
);
Pass Only What Components Need
Avoid passing large objects if a child only needs one or two properties. This makes components easier to understand and test:
// Bad: passing an entire user object
<UserInfo user={user} />
// Good: passing only the needed properties
<UserInfo name={user.name} title={user.title} />
Use PropTypes or TypeScript for Validation
For larger applications, validate props using the PropTypes library or TypeScript to catch bugs early:
import PropTypes from 'prop-types';
const UserCard = ({ userData }) => {
// component code
};
UserCard.propTypes = {
userData: PropTypes.shape({
name: PropTypes.string.isRequired,
imageUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}).isRequired
};
What Anti-Patterns Should You Avoid?
Certain patterns can make your code harder to maintain or create bugs.
Never Mutate Props
Props are read-only. If a component tries to modify a prop directly, React will not detect the change and the UI will not update. Always treat props as immutable:
// Bad: mutating props
const UserCard = (props) => {
props.userData.name = 'New Name'; // Don't do this
return <div>{props.userData.name}</div>;
};
// Good: use state if data must change
const UserCard = ({ userData }) => {
const [name, setName] = React.useState(userData.name);
return <div>{name}</div>;
};
Avoid Over-Drilling
Prop drilling becomes unwieldy when you pass props through many layers of components that do not use them. If you find yourself drilling props through 3 or more levels, consider using Context API or state management libraries like Redux:
// Excessive prop drilling
<Parent prop={value}>
<Child prop={value}>
<GrandChild prop={value}>
<GreatGrandChild prop={value} /> {/* Finally used here */}
</GrandChild>
</Child>
</Parent>
For deep component trees, Context API provides a cleaner solution.
Frequently Asked Questions
What is the difference between props and state?
Props are data passed down from a parent component to a child; they are read-only and cannot be changed by the child. State is data managed within a component using useState; it can change over time and triggers re-renders. Props flow downward; state is local to one component.
Can I pass functions as props?
Yes. Functions are values in JavaScript, so you can pass them as props. This is how child components communicate back to parents (we'll cover this in the next series). For example, <Button onClick={handleClick} /> passes a function as a prop.
What happens if I don't provide a required prop?
If a component expects a prop and it is not provided, the component will receive undefined for that prop, which can cause errors when the component tries to access properties on undefined. Use PropTypes or TypeScript to catch this during development.
Why do I need a key when rendering lists?
React uses the key to match components with their data across re-renders. Without a key, React relies on position in the array, which breaks when the list is reordered, filtered, or sorted. Always use a stable, unique identifier as the key.
When should I avoid destructuring props?
Destructuring is always safe and encouraged for clarity. The only exception is performance-critical contexts where you need to avoid creating intermediate objects, but this is rare. In most cases, destructure for readability.