Editor’s note: This article was last updated on 4 October 2023 to add information about how to pass components as data to other components using the children
prop.
As you learn how to develop web applications using React, you will inevitably come across the concept of props. Understanding the way props work is essential to mastering React, but fully grasping the concept is no easy thing.
In this article, we will delve into the essentials of props in React, discussing their use in function components, the importance of PropTypes
and defaultProps
, and the process of passing data from child to parent components. Additionally, we will address the challenges of prop drilling, and highlight common pitfalls with React props.
Jump ahead:
PropTypes
and defaultProps
children
propIf you’re a more visual learner, consider checking out our video guide:
How to pass React props to components
Learn how to pass React props to components with software developer Kevin Tomas, along with how to reuse props and state, how to use props in a function component, and more. You can find the original blog post here on the LogRocket blog: https://blog.logrocket.com/the-beginners-guide-to-mastering-react-props-3f6f01fd7099/?youtube-tutorial 00:00 LogRocket Intro 00:15 How do props work in React?
Props stand for properties, and they are used in a React application to send data from one React component to another. Let’s take a look at the example code below. Here, we have a single React component rendering a string:
import React, { Component } from "react"; import ReactDOM from "react-dom"; class App extends Component { render(){ return <div>Hello, World!</div> } } ReactDOM.render(<App />, document.getElementById("root"));
Here’s how to add props into the App
component. Right beside the call to the App component on ReactDOM.render
, type a random property and assign it a value. I will create a name
property and assign it as "Nathan"
:
import React, { Component } from "react"; import ReactDOM from "react-dom"; class App extends Component { render(){ return <div>Hello, World!</div> } } ReactDOM.render(<App name="Nathan" />, document.getElementById("root"));
With that, the App
component now has a props called name
; you can call on it from the class using this
. Let me show you how I greet myself:
import React, { Component } from "react"; import ReactDOM from "react-dom"; class App extends Component { render(){ return <div>Hello, {this.props.name}!</div> } } ReactDOM.render(<App name="Nathan" />, document.getElementById("root"));
This is the very basis of props: it allows you to send any data you can think of into a component when you call on that component. When you have two components or more, you can pass data around. Here’s another example with two components.
As the code example demonstrates, you can pass props between components by adding them when the component is being called, just like you pass arguments when calling on a regular JavaScript function
. Speaking of functions, because React allows you to create a component using function
as well, let’s see how props work in a function component next.
In a function component, components receive props exactly like an ordinary function argument. A function component will receive the props
object with the properties you described in the component call:
import React from "react"; import ReactDOM from "react-dom"; function App() { return <Greeting name="Nathan" age={27} occupation="Software Developer" />; } function Greeting(props) { return ( <p> Hello! I'm {props.name}, a {props.age} years old {props.occupation}. Pleased to meet you! </p> ); } ReactDOM.render(<App />, document.getElementById("root"));
Aside from passing multiple props at once, in this example, you also see that the age
prop is a number data type. This demonstrates that you can pass any type of data available in JavaScript — such as number
, Boolean
, or object
— into props. This is how props enable you to send data using the top-down approach, wherein a component at a higher level can send data to a component below it.
The use of props allows you to reuse more React code and avoid repeating yourself. In the case of our example, you can reuse the same Greeting
component for many different people:
import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Greeting name="Nathan" age={27} occupation="Software Developer" /> <Greeting name="Jane" age={24} occupation="Frontend Developer" /> </div> ); } function Greeting(props) { return ( <p> Hello! I'm {props.name}, a {props.age} years old {props.occupation}. Pleased to meet you! </p> ); } ReactDOM.render(<App />, document.getElementById("root"));
That’s great! But because props are read-only and must not be changed manually throughout the lifespan of a React application, using only props in your React app doesn’t really make it a dynamic app that can respond to user interactions and render accordingly. In order to do that, you need to use state
.
States and props together form the data “model” of a React application. While props are meant to be read-only, states are used for data that can change based on user actions. Let’s see how they work together to create a dynamic application.
First, let’s add a new state named textSwitch
that stores a Boolean value in the App
component and passes it to the Greeting
component. The Greeting
component will look to this state value to decide what to render. Check out the code example here.
This code example shows how you can conditionally render the view of your application based on user actions with state
and props
. In React, states are passed from one component into another component as props. Because prop names and values will just be passed into a component as regular props
object properties, it’s not concerned with where the data is coming from.
PropTypes
and defaultProps
As you develop your React application, sometimes you might need a prop to be structured and defined to avoid bugs and errors. In the same way that a function
might require mandatory arguments, a React component might require a prop to be defined if it is to be rendered properly.
You can make a mistake and forget to pass a required prop into the component that needs it:
import React from "react"; import ReactDOM from "react-dom"; function App() { return <Greeting name="Nathan" />; } function Greeting(props) { return ( <p> Hello! I'm {props.name}, a {props.age} years old {props.occupation}. Pleased to meet you! </p> ); } ReactDOM.render(<App />, document.getElementById("root"));
While props.age
and props.occupation
are undefined in the Greeting
component, React will simply ignore the expression to call on their value and render the rest of the text. It doesn’t trigger any error, but you know you can’t let this kind of thing go unaddressed.
This is where PropTypes
comes in to help. PropTypes
is a special component property that can be used to validate the props you have in a component. It’s a separate, optional npm package, so you need to install it first before using it:
npm install --save prop-types
Now let’s make the required props in the Greeting
component:
import React from "react"; import ReactDOM from "react-dom"; import PropTypes from "prop-types"; function App() { return <Greeting name="Nathan" />; } function Greeting(props) { return ( <p> Hello! I'm {props.name}, a {props.age} years old {props.occupation}. Pleased to meet you! </p> ); } Greeting.propTypes = { name: PropTypes.string.isRequired, // must be a string and defined age: PropTypes.number.isRequired, // must be a number and defined occupation: PropTypes.string.isRequired // must be a string and defined }; ReactDOM.render(<App />, document.getElementById("root"));
With the propTypes
property declared, the Greeting
component will throw a warning to the console when its props aren’t passing propTypes
validation.
You can also define default values for props in cases where props are not being passed into the component on call by using another special property called defaultProps
. Check out the updated code example.
And now the default values in defaultProps
will be used when Greeting
is called without props. Learn more about default props in React.
A parent component is any component that calls other components in its code block, while a child component is simply a component that gets called by a parent component. A parent component passes data down to child components using props.
You might wonder, “How can you pass data up from a child component to a parent component?”
The answer is it’s not possible — at least not directly. But here’s the thing in React: you can also pass function
as props. How is that relevant to the question? Let’s first return to the code example with state
:
import React, { useState } from "react"; import ReactDOM from "react-dom"; function App() { const [textSwitch, setTextSwitch] = useState(true); return ( <div> <button onClick={() => setTextSwitch(!textSwitch)} type="button"> Toggle Name </button> <Greeting text={textSwitch} /> </div> ); } function Greeting(props) { console.log(props.text); if (props.text) { return ( <p> Hello! I'm Nathan and I'm a Software Developer. Pleased to meet you! </p> ); } return ( <p>Hello! I'm Jane and I'm a Frontend Developer. Pleased to meet you!</p> ); } ReactDOM.render(<App />, document.getElementById("root"));
It’s very common for a React application to have as many as three component layers, with the top-layer component calling on a child component that calls on another child component. We need to adjust the example above a bit to illustrate this point.
Let’s move the <button>
element out of App
and into its own component. To make it simple, let’s call it ChangeGreeting
. You will then call on this component from the Greeting
component instead of the App
component:
import React, { useState } from "react"; import ReactDOM from "react-dom"; function App() { const [textSwitch, setTextSwitch] = useState(true); return ( <div> <Greeting text={textSwitch} /> </div> ); } function Greeting(props) { let element; if (props.text) { element = ( <p> Hello! I'm Nathan and I'm a Software Developer. Pleased to meet you! </p> ); } else { element = ( <p>Hello! I'm Jane and I'm a Frontend Developer. Pleased to meet you!</p> ); } return ( <div> {element} <ChangeGreeting /> </div> ); } function ChangeGreeting(props) { return ( <button type="button"> Toggle Name </button> ); } ReactDOM.render(<App />, document.getElementById("root"));
Now the button for setting the state is in the ChangeGreeting
component, which is two layers down from where the state is — at the App
component. So how can you possibly change the state? The answer is that you send a function down until it reaches the component that needs it. Check out the code example here.
In the example linked above, the App
component is sending the handleClick
prop, which has the function to change the state into the Greeting
component. The Greeting
component doesn’t actually need it, but its child component, ChangeGreeting
, does, so it forwards the prop there.
On the ChangeGreeting
component, it will call on the handleClick
function when the button is clicked, causing App
to execute the function. When the state in App
is updated, the React view is re-rendered, and the new state value is then sent to Greeting
through props.
So, yes — React can’t send data up from a child component into its parent component, but the parent component can send a function to a child component. Knowing this, you can send a function that updates the state into the child component, and once that function is called, the parent component will update the state. You can’t send data, but you can send a signal for change using a function.
The last example for passing data actually represents another common problem you might encounter when dealing with props and state: prop drilling.
Prop drilling refers to passing props down component layers until they reach the designated child component, while other higher components don’t actually need them.
It might seem OK in the example above, but keep in mind that we only have three components there. When you have many components, and all of them are interacting with each other using props and state, prop drilling can become a headache to maintain.
In order to avoid this problem, one of the things you can do is keep the number of components down and only create new components when that particular piece of component needs to be reused.
If we go back to our example, there is absolutely no need for a separate ChangeGreeting
component until a component other than Greeting
actually calls on the same piece of code. You can do this with just two components:
import React, { useState } from "react"; import ReactDOM from "react-dom"; function App() { const [textSwitch, setTextSwitch] = useState(true); return ( <div> <Greeting text={textSwitch} handleClick={() => setTextSwitch(!textSwitch)} /> </div> ); } function Greeting(props) { let element; if (props.text) { element = ( <p> Hello! I'm Nathan and I'm a Software Developer. Pleased to meet you! </p> ); } else { element = ( <p>Hello! I'm Jane and I'm a Frontend Developer. Pleased to meet you!</p> ); } return ( <div> {element} <button onClick={props.handleClick} type="button"> Toggle Name </button> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
There you go — no prop drilling is necessary for passing down the props this way.
When passing props in a component, you might be tempted to destructure them deeply, like this:
function Greeting({ data: { items: { importantData } } }) { // Use importantData return ( <div>{importantData}</div> ); }
While this can sometimes be necessary — especially if you’re dealing with deeply nested data structures — it can make your code less readable and generally more prone to errors. Deep restructuring like this can also lead to verbose and cluttered code, making it harder to scan and maintain.
Instead of destructuring deeply, it’s best to only destructure the props you actually need in your component. This makes it easier to see which props are being used:
function Greeting(props) { // Use props.data.importantData return ( <div>{props.data.importantData}</div> ); }
You can also use the spread operator to pass props directly to a child component. This way, you don’t need to destructure at all, and it makes your code more maintainable and less tightly coupled to the parent’s prop structure:
function Greeting(props) { // Use props.data.importantData return ( <div>{props.data.importantData}</div> ); }
One of the common pitfalls in React when working with props is directly modifying props within a component. This is very bad practice and can lead to unexpected behavior, bugs, and challenges in debugging. Consider the following example:
function Greeting(props) { // Attempt to modify a prop directly (this is wrong) props.data.name = "John"; return ( <div> <p>Hello, {props.data.name}!</p> </div> ); }
Modifying a prop directly can lead to the UI not updating as expected, as React relies on the immutability of props to trigger re-renders. Also, If you directly modify props and encounter issues, it can be challenging to identify the root cause because it goes against React’s principles.
If you need to work with data that can change over time, use React’s state mechanism (useState
for functional components or this.state
for class components) to manage it. State is mutable and can be updated safely:
function Greeting(props) { // Use state for mutable data const [name, setName] = useState(props.data.name); // Modify name using setName, not props setName("John"); return ( <div> <p>Hello, {name}!</p> </div> ); }
Excessive prop validation occurs when you validate every prop, even those that are straightforward or unlikely to cause issues. Keeping extensive prop validation up-to-date can be time-consuming and may lead to increased maintenance overhead as your component evolves. It can also distract from the core logic of your component and make it harder for developers to focus on the essential parts of your code.
To avoid this, it’s best to prioritize prop validation for critical props that directly affect the behavior or rendering of your component. These are props that are integral to your component’s functionality:
Greeting.propTypes = { // Critical prop: Validate its type and presence importantData: PropTypes.string.isRequired, // Less critical prop: Validate its type, but don't require it optionalData: PropTypes.number, };
For props that are simple and unlikely to cause errors, consider omitting the PropTypes
validation. For example, if you have a prop that is a string or a number and doesn’t have complex validation requirements, you may skip defining PropTypes
for it.
For complex props that require more than basic type validation, consider using custom validation functions. These functions can enforce specific rules or constraints on the prop’s value:
Greeting.propTypes = { userData: function (props, propName, componentName) { if (!props[propName] || typeof props[propName] !== 'object') { return new Error(`Invalid ${propName} in ${componentName}.`); } // Additional validation logic... }, };
children
propThe children
prop in React is a special prop that allows you to pass components or elements as data to other components. It basically renders the contents included between the opening and closing tags of a component when it is invoked, enabling you to create highly flexible and reusable components.
Here’s exactly how that works:
import React from 'react'; // Panel component const Panel = ({ title, children }) => { return ( <div className="panel"> <h2>{title}</h2> <div className="panel-content"> {children} </div> </div> ); }; // App component const App = () => { return ( <div> <Panel title="Welcome to My App"> <p>This is some content inside the panel.</p> <button>Click Me</button> </Panel> </div> ); }; export default App;
When you render the App
component in the code above, it will display a panel with the title "Welcome to My App"
and the provided contents inside the panel. The contents are passed as children to the Panel
component, allowing you to customize the contents within the panel without modifying the Panel
component itself.
The children
prop can also be used as a function; this is popularly known as “render props.” It enables components to pass a function as their child, allowing the parent component to provide dynamic data and behavior to the child component. This approach enhances component composition, reusability, and configurability. We talk more about it and compare its performance against custom hooks in this article.
As with all things about learning React, props are easy to learn but hard to master. Now you know that props are immutable (read-only) data used to make React components “talk” to each other. They are very similar to arguments passed to a function, which can be anything specified by developers themselves.
States and props enable you to create a dynamic React application with a solid codebase that is reusable, maintainable, and data-driven.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
6 Replies to "How props are passed to components in React"
Thank you for proving detail information for How to pass props to components in React. this steps is really helpful on my current project.
Curious question.
Sometimes ( Mostly 🙁 ) I do this inside child components before the return fucntion.
const {
article,
show,
created,
lines,
onClick
} = props;
Then i use this inside th return.
I do to save myself writing this.props before every variable.
Is this bad practice ?
Daniel
Hey Daniel it’s the author here. No, it’s actually common practice to destructure the props before using them, so relax 🙂
You’re welcome Kirti. Glad you find it useful
Hi, can you explain how pass props to server side in order to use the prop as a key to search a specific documento in a mongodb database ?
I do like this ({ pdf, scrollPdfFrame, zoomInOption }) in functional component