- SWBAT articulate the difference between parent and child components
- SWBAT break down a page into parent and child components
- SWBAT rearrange existing code into parent and child components
- SWBAT pass props from parent components to child components
Let's all look at this yearbook repository's code. You're welcome to clone, install, and start the app, or you can simply reference the screenshot.
Based on what you know so far, how would you accomplish the following modifications to the digital yearbook mockup for Glenbrook North High School:
- Add an "Honors" section for each student.
- Make all Senior quotes in italics.
- Move each student's name above their picture.
- How tedious did you think it would be to make those changes?
- Can you think of a more efficient way to arrange the code so that changes like those are not so laborious?
As you've seen, it's possible to break code up into functional components to make it easier for a developer to manage the code in different parts of a React app. It's worth recognizing, however, that components can use (or be used by) other components. This makes code a bit more structurally semantic (it gives it more meaning), instead of just duplicating the way you might build an app in HTML.
In order to fully prep for the Yearbook lab, let's practice on this music library app.
Explore this music library repository to demonstrate the following description of nested components. Either
clone
the repository,npm install
thennpm start
, or explore the code directly in thesrc/
subfolder.
When the Artist
component uses the Album
component as part of its return
statement, Artist
is referred to as the "parent" of Album
, and Album
is referred to as the "child" of Artist
.
The next level down, Album
could, in turn, be built from other components - say AlbumArt
and TrackList
. In this case, Album
is the "parent" of AlbumArt
and TrackList
, and AlbumArt
and TrackList
are the "children" of Album
.
So Album
both a parent and a child. It is the child of Artist
and the parent of AlbumArt
and TrackList
.
import React from 'react';
import Album from './Album';
function Artist() {
return (
<Album />
);
}
export default Artist;
import React from 'react';
import AlbumArt from './AlbumArt';
import TrackList from './TrackList';
function Album() {
return (
<div>
<AlbumArt />
<TrackList />
</div>
);
}
export default Album;
import React from 'react';
function AlbumArt() {
return (
<div>
// Some code
</div>
);
}
export default AlbumArt;
import React from 'react';
function TrackList() {
return (
<div>
// Some code
</div>
);
}
export default TrackList;
The parent-child relationship is all about nesting.
What happens when you:
- add an additional
Artist
inApp.js
? - add an additional
Album
inArtist
? - add an additional
AlbumArt
inAlbum
?
- Revisit the app we started with, and think about how it could be rearranged into nested components.
- Map out (on paper) the nested structure of the app into granular sub-components. What is/are the most granular functional components you would make?
Now that you've had a chance to think about how to restructure the app above, try to implement that restructuring:
- Break apart the code into functional components
- Tip: start by breaking the code into large components, then try to break those components into smaller components.
- Keep an eye out for any code you might be able to package into a component and reuse.
- Keep breaking the code into separate functional components until you are satisfied that the functional components are as granular as they should be.
- Remember to include the necessary
import
andexport
statements when you create new functional components:
import React from 'react'
import AnotherComponent from './AnotherComponent'
const ComponentName = () => {
return (
// Some code
)
}
export default ComponentName
- Remember to include a new
import
statement wherever you use a new functional component. - Remember to replace the code you abstract into the functional component with the component name: e.g.
<ComponentName />
.
Now that you've seen how to break up a page into components, let's take a step back and think about the data that flows into a webpage which is rearranged into what is displayed on that page. This data most often comes as a list, array, or object, where each component that is shown is a list entry and each entry contains all of the information necessary for the component.
You've already seen how functions like .map()
can make quick work of iterating over an array in order to return a new array or new HTML. By extension, .map()
can also be used to quickly generate the JSX for components in a React app.
But, as you've just learned above, components can be made from nested components which themselves may nest components. To understand how that top-level data is passed to the correct component which will display it, we need to understand "prop drilling". "Prop drilling" is how props can be passed from parent to child, then to a child of that child, and so on until the value reaches the depth containing the component where it is displayed.
Let's revisit our nesting example above to see how data could be passed from Artist
through Album
to AlbumArt
and TrackList
. Consider the following components:
import React from 'react';
import Album from './Album';
function Artist() {
// This data might come from a database or a music service like spotify.
// You can find it in the data.js file and copy it over if you like.
// You'll obviously want to take a moment and values to match one of your favorite albums.
let data = {
"artist": "My favorite singer",
"album": "Their best album",
"image": "albumCover.png",
"tracks": [
{
"id": 1,
"title": "Opening Song",
"duration": "2:31",
"comment": "makes me want to dance"
},
{
"id": 2,
"title": "Second Song",
"duration": "3:12",
"comment": "always skip this one"
},
{
"id": 3,
"title": "Third Song",
"duration": "5:33",
"comment": "my favorite!"
}
]
}
return (
<div>
<h1>{data.artist}</h1>
<Album data={data} />
</div>
);
}
export default Artist;
Artist
receives the data
(from some database or some API call) and renders the artist name in an <h1>
. However, the rest of the data isn't used in Artist
, so the entire data object is passed as a prop called data
to Album
.
import React from 'react';
import AlbumArt from './AlbumArt';
import TrackList from './TrackList';
function Album(props) {
return (
<div>
<h3>{props.data.album}</h3>
<AlbumArt image={props.data.image}/>
<TrackList songs={props.data.tracks}/>
</div>
);
}
export default Album;
Album
receives the data
as props, and renders an <h3>
showing the name of the album. Note: we could imagine making some small updates to this code in order to show multiple albums, right?
The remaining data, however, doesn't get used by Album
. In this case AlbumArt
might show an image of album artwork and TrackList
could show a list of songs. Data from props
is passed to each of those components as image
and songs
, respectively, but those props could have been called anything.
import React from 'react';
function AlbumArt(props) {
// Bear in mind that since we haven't actually uploaded an image, this will likely render as a broken image at first.
return (
<div>
<img src="{props.image}" />
</div>
);
}
export default AlbumArt;
AlbumArt
receives a single prop, image
, and then uses it to render the album artwork. There are no more nested components that will need any additional data here...
import React from 'react';
function TrackList(props) {
return (
<ul>
{
props.songs.map(tune => {
return (<li>{tune.title}</li>);
})
}
</ul>
);
}
export default TrackList
TrackList
receives a single prop, songs
, which is an array of objects listing out the songs in that album. You could imagine using .map()
to iterate over that list to make a bulleted list of tune titles (shown), or even to make it more complex so you could link out to play the songs, etc. But after this component, there are no more nested components that will need any additional data.
NOTE: This .map()
used an explicit return statement where we actually write return
, which means we sort of have two returns total in this component - one small one for the map method, and the other for the entire TrackList
component. However, since the return statement for the map method is a single line of JSX, that could actually be rewritten as a single line:
{props.songs.map(tune => {<li>{tune.title}</li>})}
However, this single-line refactoring won't be as useful if you want to start adding additional features to each of these tracks (like showing duration, your comments, or a link out to a youtube video of the song).
Think back to the three changes that you were asked to make at the beginning of this lesson to the Yearbook app.
- What components would we need to make for the yearbook app?
For the next lab, break that yearbook app up into functional components, and then make these changes:
- Add an "Honors" section for each student.
- Make all Senior quotes in italics.
- Move each student's name above their picture.
It's possible that you may have to duplicate components that are used elsewhere in order to make changes in only certain circumstances. Don't forget to create a new component name, export it, and import it (where necessary).
- Was it easier/harder to make the changes now that you've restructured the app?
- What are the challenges to working with an app that has a lot of child components, many of which might have child components, some of which might have child components?