This course is archived!
While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).
Child Component
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur RepLogApp
component is getting kinda big! I'm so proud! It's not only the amount of HTML, but also its complexity. We're now handling an event, updating state in a handler function and, below, this repLogs
row stuff is pretty complex on its own!
In PHP, if you're working on a class, sometimes that class can become so big or so complex that, for your own sanity, you choose to create a new class and move some of that logic into it. Another reason you might create a new class is if you want to make some of your logic re-usable.
Well, that exact idea is true in React: when a component becomes too big or too complex & confusing, you can choose to move part of it into a new component. This isn't some ground-breaking strategy: it just simple code organization! And, in theory, you could re-use the new component in multiple places.
Creating the RepLogList Component
Because the rep log table is already pretty complex, let's move that into its own component first. In the RepLog
directory, create a new file: how about, RepLogList.js
. Inside, every React component begins the same way: import React
and { Component }
from react
. Then, export default class RepLogList extends Component
. Add the one required method: render()
.
import React, { Component } from 'react'; | |
// ... line 2 | |
export default class RepLogList extends Component { | |
render() { | |
// ... lines 5 - 26 | |
} | |
} |
So... hmm... I basically want to move my rep log rows into that component. We could move the whole table, or just the inside - don't over-think it. Let's copy all of the tbody
. Then, return, add parenthesis so we can use multiple lines and, paste!
Cool! Of course, we're missing the repLogs
variable! Right now, because that's still hardcoded, let's just move that variable over into the render()
method of the new component.
// ... lines 1 - 3 | |
render() { | |
const repLogs = [ | |
{ id: 1, reps: 25, itemLabel: 'My Laptop', totalWeightLifted: 112.5 }, | |
{ id: 2, reps: 10, itemLabel: 'Big Fat Cat', totalWeightLifted: 180 }, | |
{ id: 8, reps: 4, itemLabel: 'Big Fat Cat', totalWeightLifted: 72 } | |
]; | |
return ( | |
<tbody> | |
{repLogs.map((repLog) => ( | |
<tr | |
key={repLog.id} | |
className={highlightedRowId === repLog.id ? 'info' : ''} | |
onClick={(event) => this.handleRowClick(repLog.id, event)} | |
> | |
<td>{repLog.itemLabel}</td> | |
<td>{repLog.reps}</td> | |
<td>{repLog.totalWeightLifted}</td> | |
<td>...</td> | |
</tr> | |
))} | |
</tbody> | |
) | |
} | |
// ... lines 28 - 29 |
But, we do still have one problem: highlightedRowId
. Um, ignore that for a minute. Back in RepLogApp
, delete the tbody
. At the top, this is cool: import RepLogList from './RepLogList'
. And because RepLogList
is a component, we can render it just like we did with RepLogApp
: go into the middle of the markup and add <RepLogList />
.
// ... line 1 | |
import RepLogList from './RepLogList'; | |
// ... line 3 | |
export default class RepLogApp extends Component { | |
// ... lines 5 - 16 | |
render() { | |
// ... lines 18 - 25 | |
return ( | |
// ... lines 27 - 29 | |
<table className="table table-striped"> | |
// ... lines 31 - 38 | |
<RepLogList/> | |
// ... lines 40 - 47 | |
</table> | |
// ... lines 49 - 86 | |
); | |
} | |
} |
Leaving State at the Top Level (for now)
That's it! We have successfully broken our big component into a smaller piece. Well, I guess we shouldn't celebrate too much, because, when we refresh, in the console, yep! React is always trying to bring us down: the highlightedRowId
variable is not defined in RepLogList
!
That makes perfect sense: our child component - RepLogList
- needs to know this value so that it can add the info
class. But... hmm... we have a problem! The highlightedRowId
state lives in a different component: our top-level RepLogApp
component! So, how can access the state of our parent component?
Well, before I answer that, there is technically another option: we could just move the highlightedRowId
state into the RepLogList
component. And, technically, this would work! Look closely: RepLogApp
isn't using that data anywhere else! So if we moved the state, everything would work!
But... for a reason I can't fully explain yet, I don't want you to do that. Nope, I want you to leave all of your state in the top level component of your app. That means, I want all of your child components to have zero state. Don't worry: we'll talk a lot more about why later.
Passing Props down the Tree
But, because I'm being rude and forcing you to keep all of your state in RepLogApp
, the question becomes: how can we pass this highlightedRowId
state into RepLogList
?
Guess what? We already know the answer! We already know how to pass data into a component: props. We have the highlightedRowId
variable that's coming from state. Scroll down to RepLogList
and add a new prop: highlightedRowId={}
and pass that variable.
// ... lines 1 - 38 | |
<RepLogList highlightedRowId={highlightedRowId}/> | |
// ... lines 40 - 90 |
And now we can go back into RepLogList
and use this in render()
! At the top, let's continue to destructure our props & state: const { highlightedRowId } = this.props
. And, just like earlier, ignore this error about props validation: we'll talk about that soon.
// ... lines 1 - 3 | |
render() { | |
const { highlightedRowId } = this.props; | |
// ... lines 6 - 28 | |
} | |
// ... lines 30 - 31 |
Ok... we're done! Move back to your browser and, refresh! It works! And if you check out the React dev tools, you can still see RepLogApp
on top... but down here, hey! There is the embedded RepLogList
. Now, things get fun: click back on RepLogApp
and change the state to 2. This causes React to re-render that component. Check out RepLogList
again - yea! You can see that its prop automatically updated!
This highlights one really, really important detail: while you may have multiple components that have some state, each piece of state like the highlightedRowId
- needs to live in exactly one component. What I mean is: you are not allowed to have, for example, a highlightedRowId
state in RepLogApp
and also a highlightedRowId
state in RepLogList
. Nope! That would duplicate that data. Instead, each piece of state will live in just one component. And then, if a child component needs that data, we'll pass it as a prop.
Props are Immutable... but Change
We already know that whenever something updates the state of a component, React automatically re-renders that component by calling render()
. And actually, the same is true for props. When the highlightedRowId
state changes, this changes the props of RepLogList
and that causes it to also re-render. Which, is exactly what we want!
But, earlier, I told you that props are immutable: that props can never be changed. That's true, but it's maybe not the best way to explain it. In RepLogApp
, when the highlightedRowId
state changes, we will pass a new value to RepLogList
for the highlightedRowId
prop. But, here's the important part: once RepLogList
receives that prop, it never changes it. You will never change something on this.props
.
We're going to see this pattern over and over again: we hold state in one component, change the state in that component and then pass that state to any child component that needs it as a prop. And now we know that when that state changes, all the child components that use it will automatically re-render.
But... our click handling code is now broken! Let's fix it!
Hello, why we call RepLogList Com is a child of RepLogApp? in OOP Concept, if class B is a child of class A, we must first define the relationship (using 'extends' key word for example), because class B can recognise its parent class and inherits properties and functions from the base class. However, in OOP, class A can use class B by declaring an object from class B inside it but we call this Class A 'USE or has-a relationship' class B. So here why we do not say RepLogApp Com uses or has-a RepLogList Com? instead of saying RepLogList is a child of RepLogApp Com?