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 to grasp the concept fully is no easy thing.
Introduction
Props stands for “properties,” and they are used in a React application to send data from one React component to another React component. 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"));
Now here’s how you 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"));
And 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 above 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. And speaking of functions, since React allows you to create a component using function as well, let’s see how props work in a function component next.
Props in a function component
In a function component, components receive props exactly like an ordinary function argument. A function component will receive the props object with 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 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.
Code reuse with props and state
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 since 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 to the App component and pass it to the Greeting component. The Greeting component will look to this state value to decide what to render:
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. Since 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 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 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 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:
And now the default values in defaultProps will be used when Greeting is called without props.
Passing data from child components to parent components
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 a 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:
In the example above, the App component is sending the handleClick prop, which has the function to change the state into the Greeting component. The Greeting component didn’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, 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 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.
Prop drilling and how to deal with it
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 the 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.
Back to the example, there is absolutely no need for a separate ChangeGreeting component until another component besides Greeting actually calls on the same piece of code. You can do this with only 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 necessary for passing down the props this way.
Conclusion
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.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
The post The beginner's guide to mastering React props appeared first on LogRocket Blog.
Top comments (0)