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).
Reusable Components
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 SubscribeReact's entire architecture is centered around making components that are reusable. This is especially easy to see with the dumb, presentational components: all they do is receive props and... render! It would be very easy to render those components with different props in different places.
But, in reality, a lot of components aren't really meant to be reused: RepLogCreator
, RepLogList
and RepLogs
... yea, it's pretty unlikely we'll use those on other parts of our site... except maybe for RepLogCreator
, which could be used to edit rep logs.
But, there are a lot of nice use-cases for building truly, re-usable components, basically, tools. For example, it's pretty simple, but, suppose we had a lot of Bootstrap submit buttons and we want to turn the submit button into its own React component. We can totally do that, and it's pretty awesome.
In the assets/js
directory, create a new directory called components/
and inside, a new file called Button.js
. I'm not putting this in RepLog
because this could be used on other parts of the site.
This will be a dumb, presentation component. So, copy the import lines from the top of RepLogCreator
, and then say export default function Button
with the normal props
argument. Inside, return the markup for a button with className="btn"
, because every button at least has that class.
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
export default function Button(props) { | |
return ( | |
<button | |
className="btn" | |
// ... lines 8 - 9 | |
); | |
} | |
// ... lines 12 - 16 |
Spreading the Props
Go back to RepLogCreator
and scroll down. Ok, this button has type="submit"
. We could add that to our Button
component, but not all buttons need this. But, no worries: we can allow this to be passed in as a prop! In fact, we might need to pass a bunch of different attributes as props.
To allow that, use the attribute spread operator ...props
. It's simple: any prop passed to this component will be rendered as an attribute. And for the text, hmm: how about a prop called text
: props.text
. Close the button tag. At the bottom, add Button.propTypes =
and define text
as a string that's required.
// ... lines 1 - 3 | |
export default function Button(props) { | |
return ( | |
<button | |
// ... line 7 | |
{...props} | |
>{props.text}</button> | |
); | |
} | |
// ... line 12 | |
Button.propTypes = { | |
text: PropTypes.string.isRequired | |
}; |
Perfect!
Back in RepLogCreator
, head up top and bring this in: import Button
from ./Components/Button
.
// ... lines 1 - 2 | |
import Button from '../Components/Button'; | |
// ... lines 4 - 100 |
Then all the way down at the bottom, use <Button type="submit" />
and also the text
prop. Copy the original text and paste it here.
// ... lines 1 - 46 | |
render() { | |
// ... lines 48 - 50 | |
return ( | |
// ... lines 52 - 88 | |
<Button type="submit" text="I Lifted it!" /> | |
// ... line 90 | |
); | |
} | |
// ... lines 93 - 100 |
We are going to temporarily lose the btn-primary
class. That is a problem, and we'll fix it soon. Delete the old button. This should work! Move over and refresh! There it is! The button has the class, type="submit"
and the text. Hmm, but it also has a text=
attribute... which makes perfect sense: we added that as a prop! Of course, we don't actually want that attribute, so we'll need to fix that.
Using props.children
But first, we have a bigger problem! What if I wanted to add a Font Awesome icon to the text? Normally we would add a <span className="">
and then the classes. But... this doesn't look right: I'm putting HTML inside of this string. And, actually, this wouldn't even work, because React escapes HTML tags in strings.
New idea: what if we could remove this text
prop and treat the Button
like a true HTML element by putting the text inside. That looks awesome. This is not only possible, this is ideal! By doing it this way, we can include text, HTML elements or even other React components! Woh!
If you pass something in the body of a Component, that is known as its children, and you can access it automatically with props.children
. It's that simple.
// ... lines 1 - 4 | |
return ( | |
<button | |
// ... lines 7 - 8 | |
>{props.children}</button> | |
); | |
// ... lines 11 - 12 |
Oh, and ESLint is angry because we're missing props validation for children
. I'm going to ignore that because the children prop is a special case and, we don't really care of its text, a component or something else. But, you could add it with the PropTypes "any" type.
Remove the propTypes for now. Let's try it! Move over and refresh! Looking good. To prove that using children is awesome, add a new <span>
with className="fa fa-plus-circle"
.
// ... lines 1 - 88 | |
<Button type="submit"> | |
I Lifted it <span className="fa fa-plus-circle"></span> | |
</Button> | |
// ... lines 92 - 102 |
Go check that out. Nice!
Merging Prop Values in your Component
Ok, we're still missing the btn-primary
class. This is a bit trickier. We can't just pass a className
prop here, because, in the Button
, we're already passing a className
prop. So, let's just be smarter! Enter JavaScript, add ticks, then print btn
, then ${props.className}
.
// ... lines 1 - 4 | |
return ( | |
<button | |
className={`btn ${props.className}`} | |
// ... lines 8 - 9 | |
); | |
// ... lines 11 - 19 |
That should do it! We're not passing this prop yet, but try it: refresh. Oh man, undefined
! Of course! Let's go clean things up.
First, add Button.propTypes
to advertise that we accept a className
prop that's a string. We could make this required... or we can allow it to be optional, but fix that undefined
problem. To do that, set Button.defaultProps
to an object with className
set to empty quotes.
// ... lines 1 - 11 | |
Button.propTypes = { | |
className: PropTypes.string | |
}; | |
Button.defaultProps = { | |
className: '' | |
}; |
Problem solved! Try it again. Wait! What? Now the class
attribute is empty? How is that even possible? To see why, go back to RepLogCreator
and pass a className
prop here: btn-primary
.
// ... lines 1 - 88 | |
<Button type="submit" className="btn-primary"> | |
// ... lines 90 - 102 |
Go refresh again. Huh, now it has that class... but not the btn
class. Here's the deal: sure, we have this className
prop here. But, thanks to the ...props
, the className
prop we're passing in overrides the first one! We could move the ...props
first, but, in general, we do want to allow whoever uses this component to override its props.
So, hmm: we basically want to print all of the props here... except for className
. We can do that, and it's cool! Up top, let's destructure: const {} = props
, then get out className
. Use that below.
Then - this is the cool part - destructure a second variable, ...otherProps
. Use that below in the button.
// ... lines 1 - 4 | |
const { className, ...otherProps } = props; | |
// ... line 6 | |
return ( | |
<button | |
className={`btn ${className}`} | |
// ... lines 10 - 11 | |
); | |
// ... lines 13 - 21 |
Yep, the ...otherProps
will be all of the props, except for any that we specifically destructured before it. It's an awesome little trick.
Ok, try it out: move over, refresh and... we got it! It looks perfect! I hope this tiny component gets you excited about the possibilities of reusing code with React.
Hey Guys,
Is there a way to deal with phpstorm and the
ESLint: 'children' is missing in props validation
?