- Use destructuring to access props more easily
- Declare default values for destructured props
In this lesson, we'll cover props in further detail and explore how they help us make our components more dynamic and reusable. We'll also talk about destructuring and how to give our props default values.
Props allow us to pass values into our components. These values can be anything: strings, objects (including arrays and functions), and so on. They give us the opportunity to make our components more dynamic, and a lot more reusable.
For example, say we have a <MovieCard />
component. A movie has a title, a
poster image, and many other attributes (or prop-erties!). Let's examine
what this <MovieCard />
component would look like with hardcoded, static
data vs. dynamic prop data. Here's the static, hard-coded version:
function MovieCard() {
return (
<div className="movie-card">
<img
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTn1OTYGz2GDC1XjA9tirh_1Rd571yE5UFIYsmZp4nACMd7CCHM"
alt="Mad Max: Fury Road"
/>
<h2>Mad Max: Fury Road</h2>
<small>Genres: Action, Adventure, Science Fiction, Thriller</small>
</div>
);
}
Mad Max: Fury Road is a ridiculously good movie, but what if we want to render a movie card for another movie? Do we just write another component? No, that would be silly! Instead, we write our components so that they make use of props, which are passed from their parents.
To pass props to a component, you add them as attributes when you render the component, just like adding attributes to an HTML element:
const movieTitle = "Mad Max"
<MovieCard title={movieTitle} />
The value of the prop is enclosed in JSX curly braces. As we read before, this value can be anything: a variable, inline values, functions, etc. If your value is a hard-coded string, however, you can enclose it in double quotes instead:
<MovieCard title="Mad Max" />
Armed with that knowledge, let's update MovieCard
to make it dynamic by
using props:
// parent component
function App() {
const title = "Mad Max";
const posterURL =
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTn1OTYGz2GDC1XjA9tirh_1Rd571yE5UFIYsmZp4nACMd7CCHM";
const genresArr = ["Action", "Adventure", "Science Fiction", "Thriller"];
return (
<div className="App">
{/* passing down props from the parent component */}
<MovieCard title={title} posterSrc={posterURL} genres={genresArr} />
</div>
);
}
// child component
function MovieCard(props) {
return (
<div className="movie-card">
<img src={props.posterSrc} alt={props.title} />
<h2>{props.title}</h2>
<small>{props.genres.join(", ")}</small>
</div>
);
}
Now, does that not look cleaner and more reusable compared to the hard-coded version?
Since we know that a React function will only ever get called with one argument, and that argument will be the props object, we can take advantage of a modern JavaScript feature called destructuring to make our component even cleaner:
function MovieCard({ title, posterSrc, genres }) {
return (
<div className="movie-card">
<img src={posterSrc} alt={title} />
<h2>{title}</h2>
<small>{genres.join(", ")}</small>
</div>
);
}
In this example, we're destructuring the props
object in the parameter in
this function, which will have title
, posterSrc
, and genres
as keys.
Destructuring takes the keys from the props object and creates variables
with the same names. That way, in our JSX, we don't have to use
props.whatever
everywhere — the value associated with each key is stored in
the corresponding variable, so it can be accessed directly!
Another benefit of destructuring props is that it makes it easier to tell what props a component expects to be passed down from its parent. Consider these two versions of the same component:
// Without Destructuring
function MovieCard(props) {
return (
<div className="movie-card">
<img src={props.posterSrc} alt={props.title} />
<h2>{props.title}</h2>
<small>{props.genres.join(", ")}</small>
</div>
);
}
// With Destructuring
function MovieCard({ title, posterSrc, genres }) {
return (
<div className="movie-card">
<img src={posterSrc} alt={title} />
<h2>{title}</h2>
<small>{genres.join(", ")}</small>
</div>
);
}
Looking at the version without destructuring, we'd have to find all the places
where props
is referenced in the component to determine what props this
component expects. Looking at the version with destructuring, all we have to do
is examine the function parameters and we can see exactly what props the
component needs.
We can also do some more advanced destructuring in cases when our props also contain nested objects. For example:
function App() {
const socialLinks = {
github: "https://github.com/liza",
linkedin: "https://www.linkedin.com/in/liza/",
};
return (
<div>
<SocialMedia links={socialLinks} />
</div>
);
}
function SocialMedia({ socialLinks }) {
return (
<div>
<a href={socialLinks.github}>{socialLinks.github}</a>
<a href={socialLinks.linkedin}>{socialLinks.linkedin}</a>
</div>
);
}
Since socialLinks
is an object, we can also destructure it to make our JSX
cleaner, either by destructuring in the body of the function:
function SocialMedia({ socialLinks }) {
const { github, linkedin } = socialLinks;
return (
<div>
<a href={github}>{github}</a>
<a href={linkedin}>{linkedin}</a>
</div>
);
}
...or by destructuring further in the parameters to our function:
function SocialMedia({ socialLinks: { github, linkedin } }) {
return (
<div>
<a href={github}>{github}</a>
<a href={linkedin}>{linkedin}</a>
</div>
);
}
How much destructuring you do, and where you do it, is very much a matter of preference. Try it out in some components and see what feels right to you!
Let's switch gears here and imagine we are using our application to render a list of hundreds of movies. Let's also assume the data set we have is not always reliable when it comes to the urls of the movie posters.
In this case, we want to make sure our component doesn't render as an utter disaster when the data is incomplete. In order to do this, we can use a default value to assign a poster url when either a bad one, or none at all, is provided. For this example, let's use the poster for Max Headroom as a default, seeing as it is a perfect placeholder:
Instead of passing in that default poster image in case we don't have one, we
can tell our MovieCard
component to use a default value if the posterSrc
prop was not provided. To do this, we can add the default value to our
destructured props:
function MovieCard({
title,
genres,
posterSrc = "https://m.media-amazon.com/images/M/MV5BOTJjNzczMTUtNzc5MC00ODk0LWEwYjgtNzdiOTEyZmQxNzhmXkEyXkFqcGdeQXVyNzMzMjU5NDY@._V1_UY268_CR1,0,182,268_AL_.jpg",
}) {
return (
<div className="movie-card">
<img src={posterSrc} alt={title} />
<h2>{title}</h2>
<small>{genres.join(", ")}</small>
</div>
);
}
Now, whenever we omit the posterSrc
prop, or if it's undefined
, the
MovieCard
component will use this default value instead. That means we don't
have to worry about not passing in a poster all the time — the component will
take care of this for us!
For example, this version of our component would still display its default
image, even though we aren't passing a prop of posterSrc
from the parent
component:
function App() {
const title = "Mad Max";
const genresArr = ["Action", "Adventure", "Science Fiction", "Thriller"];
return (
<div className="App">
{/* posterSrc is omitted, so the default value will be used instead */}
<MovieCard title={title} genres={genresArr} />
</div>
);
}
An alternative way we could have handled bad urls would be to have MovieCard
's
parent component check whether the posterSrc
was valid/present, and then
pass some control value as a prop when it renders MovieCard
. This is not ideal
compared to using a default prop within the MovieCard
component.
Consider the following: in React, we want components to encapsulate the
functionality that they can and should be responsible for. Should the parent
component of MovieCard
be responsible for managing the assignment of a default
movie poster source value? In our example, we think not. It makes more sense for
the component that is responsible for rendering the movie information and poster
to handle missing data.
By using destructuring in our components, we can make the returned JSX
elements easier to read, because we don't need to reference props.whatever
everywhere.
Destructuring also makes it easier to tell what props a component expects, since we can just look at the destructured parameters instead of reading through the whole component and looking for references to the props object.
When we use destructuring, we can provide a default value for any prop keys we want, so that if the component doesn't receive those props from its parents, we can still render some default information.