Title: Passing Props
Duration: 3hrs+
Creator: Joe Keohan
- Create and pass props to Components
- Loop over an array of data and pass multiple props
- Work with additional JSX rules.
Having worked with functions we know that they are meant to be reusable. Part of the reusability is in accepting arguments, performing an action and returning a value.
Standard Input
produces Standard Output
Now consider that our application contains many Components, some of which may require data points in order to render the UI.
The data we pass from a parent > child Component
are called: props
. Make note that it is the parent that passes props to a child.
React data flow is unidirectional
and can only be passed down, and never directly from child
to parent
or sibling
to sibling
.
Every Component has props
and that is how data is passed from a parent to a child Component.
Let's revisit our previous Bootstrap Cards in React Developer Tools
see if anything props
related pops out.
Here is the current React Architecture as it relates to Card1.
If we highlight the Card1
Component we will see something called props
to the right.
Since we haven't yet passed any data to these Components there is nothing to show.
End Goal
Once we have implemented all the steps in today's lecture on props
React Dev Tools will look more like:
Here is a live version of today's solution.
Let's extend the rules we defined previously for creating Components and working with JSX to now include props
.
🚔 Props adhere to the following rules:
- Data is unidirectional passed down from a
parent
>child
- All Props passed to a child are organized into a single object in the child Component
- Props are
immutable
and cannot be reassigned a new value within the receiving child Component
This Rule isn't regarding props but something we will need to keep in mind going forward:
- Any Components created using Array.map() must be assigned a key prop with a unique value.
Say for instance we wanted to render the names that the images represents in our cards example. We could go directly to CardBody
and do the following:
<h5 class="card-title">Santorini</h5>
This is a fairly manual process and wouldn't be efficient if we had 100 or 1000 cards to render.
So let's add a prop
to CardBody and pass it the value of Santorini
.
A prop
is written in a name=value
format like the other html attributes your used to writing such as:
<!-- The src property of a image tag -->
<img src="someurl" />
<!-- The class property assigned to any element -->
<div class="container"></div>
<!-- The href property assigned to an anchor tag -->
<a href="someurl"></a>
Since the Card1
component is the parent that renders CardBody
than it must pass the prop to it's child.
🚔 - Data is unidirectional in React passed down from a parent
> child
Component
Let's assign CardBody the following prop
.
<CardBody title="Santorini"/>
Nothing should really change as we have already updated CardBody with that title name. So we need to update the CardBody Component to accept props.
The first thing we need to do is add the keyword props
as a parameter. So inside CardBody.js make the following change.
Let's make sure that our Component is being passed the title
prop by adding a console.log.
const CardBody = (props) => {
console.log('this is props:', props)
// ...rest of the code
}
In DevTools you should see the following in the console.
We can see here that props
is an object and that title
is a key. This will be the same pattern for when we start passing in multiple props. Each prop passed will be assigned a key:value pair.
Let's take a moment to edit our previous Bootstrap Cards CodeSandbox
and try to reassign props.
- Open the
CardBody
Component and add the following:
console.log('current props.title', props.title);
// ATTEMPT TO REASSIGN PROPS A NEW VALUE
props.title = 'Mykonos';
console.log('new props.title', props.title);
Refresh the page and you should see the following:
So it looks like props was not updated to reflect the edit. This is an example of one of the rules of props:
🚔 - Props are immutable which means you can't reassign them within the receiving child Component
So any attempt to change those props directly within the Component will have no effect.
Now that we have confirmed we are being passed the value we need for the title let's use it to replace the hard coded value.
Let's try and use the prop that was passed.
That didn't see to work out as planned. It seems it outputs props.title
and not the value
It seems we forgot about one of the rules of JSX:
🚔 Any JavaScript code that needs to be executed in JSX must be enclosed in opening/closing curls braces {}
<h5 className="card-title">{props.title}</h5>
Confirm in React Dev Tools that CardBody is now being passed a prop
👍 Click on the thumbs up when your done.
We could do the same for all the additional values we wish to pass but as you can imagine if we had 10, 20, 100+ cards this manual method becomes completely inefficient.
Also it is more likely that the data we will be using to render the cards will be imported either via a file or returned from an API call.
Either way we should expect that if we will need to create multiple cards that the data will be stored as an array of objects: [{}, {}]
Let's use some real data and replace the generic placeholder text.
- Create a new file in
src
calledcardData.js
- paste the following code into the file
export default [
{
img: "https://images.unsplash.com/photo-1536514072410-5019a3c69182?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60",
title: "Santorini",
text: "This was one of the most amazing places I've ever seen. A must see for eveyrone",
url: "https://unsplash.com/s/photos/santorini"
},
{
img: "https://images.unsplash.com/photo-1498712964741-5d33ab9e5017?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=600",
title: "Zakynthos",
text: "This was like being a pirate and we were looking to bury our treasure. It was so isolated and beautiful. ",
url: "https://unsplash.com/s/photos/santorini"
}
]
- Import the data into
App.js
ascardsArr
and add aconsole.log
to confirm it was imported.
// IMPORT DATA
import cardsArr from './cardsData'
console.log('this is cardsArr:', cardsArr)
👍 Click on the thumbs up when your done.
With the data in hand we can now loop over the array and render as many Card Components
as we need and pass them the props needed for each card.
Inside of the App
Component we will loop over the cardsArr
array and create multiple Cards in one shot.
Each prop is defined on it's own and passed it's corresponding value.
Let's also console the cards
so see what React magic has been performed.
const cards = cardsArr.map((ele, index) => {
return (
<Card1
img={ele.img}
title={ele.title}
text={ele.text}
url={ele.url}
/>
);
})
console.log('this is cards:', cards);
Now take a look in DevTools and you should see the following:
Each object appears to contain much more info then we passed and each one has a typeof
set to Symbol(react.element)
. Symbols
were a new data type introduced in ES6 and are meant to be unique, meaning there will not be Symbol
in this array with the same exact info.
Something will be needed to distinguish it as unique. React does so by assigning a key called key
.
It is currently set to null
and React will warn us when we render the Cards based on the following rule:
🚔 Any Components created within an Array.map() must be assigned a unique key.
Before we render the Cards we first need to update Card1
to pass the data down the props it's received to it's corresponding children.
const Card1 = (props) => {
console.log('this is props:', props)
return (
<div className="card" style={{width:'18rem'}}>
<CardImage img={props.img} />
<CardBody title={props.title} text={props.text} url={props.url} />
</div>
)
}
CardBody
Let's update CardBody
to make use of the props.
const CardBody = (props) => {
return (
<div className="card-body">
<h5 className="card-title">{props.title}</h5>
<p className="card-text">{props.text}</p>
<Button url={props.url}/>
</div>
)
}
Now it's your turn.
Take a moment to update the following Components to accept and use props:
- CardImage
- Button
Keep in the mind the following:
- Both Components need to include a parameter which we will always call
props
. - Any JS rendered in JSX must be wrapped in {}
👍 Click on the thumbs up when your done.
Now it's time to see all that code refactoring done to Card1
in action.
In App comment out <Card1 />
and <Card2 />
. We will now replace those values with the data returned via the .map() and stored in cards
.
<section className="cards">
{cards}
{/* <Card1 title="Santorini" />
<Card2 /> */}
</section>
As you may recall it was mentioned earlier that the elements would render fine however React would present an error. The following error to be exact:
This can easily be fixed by assigning a key
prop to each element with a unique value. Since each element in an array is assigned a unique index value we will opt to use that.
const cards = cardsArr.map((ele, index) => {
return (
<Card1
img={ele.img}
title={ele.title}
text={ele.text}
url={ele.url}
key={index}
/>
);
})
Since the goal of a Component is to be reusable we could now use Card1 as our base template for rendering as many cards as we need.
It makes more sense to rename Card1.js
to Card.js
and update both the import statement and the Component name in the map.
Below includes those 2 updates.
And there you have it. Multiple Components rendered via a loop with each being the props they need to render appropriately.
As we have made some design changes let's take a look at our final React Architecture design that takes into account all Components and the props being passed.
The above was created using Google Draw
The architecture represents all the Components and the props that are being passed to each one. This makes it much easier to understand the flow of data in our app.
Since passing props is a requirement in React there are a few shortcuts we can make when passing them.
The first is that we can use the ...spread
operator to pass many key:value's down instead of writing them out one at a time.
In Card.js
let's replace all those hard coded props, except key
, with the ...spread
operator.
const cards = cardsArr.map((ele, index) => {
return (
<Card
{...ele}
key={index}
/>
);
})
The other shorthand we can use is to update the Child components to create variables based on the key:value pairs that are in the props
object.
CardBody
Let's update CardBody
to make use of Object Destructuring. Here we use an object as parameter that includes all the prop key names that are being passed down.
const CardBody = ({title, text, url}) => {
return (
<div className="card-body">
<h5 className="card-title">{title}</h5>
<p className="card-text">{text}</p>
<Button url={url}/>
</div>
)
}
Take a moment to update the CardImage
and Button
Component to make use of Object Destructuring.
👍 Click on the thumbs up when your done.
Here is the Final Solution
The instructor will perform a walk through of organizing the Components into a folder structure.
The instructor will provide the lab