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 |
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.
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!
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 Skylar!
Yes...ish - I checked into this too, because it also bothered me. Basically, as far as I can tell, if you care enough, you simply *do* need to manually define it as a valid prop - e.g. https://github.com/yannickc... - here's even more background - https://github.com/yannickc...
Cheers!
Will there be a problem if I add this to the Button.propTypes?
Button.propTypes = {<br /> <b>children: PropTypes.node.isRequired,</b><br /> className: PropTypes.string<br />};
I don't think there should be a problem unless the Button component doesn't receive any children passed in
Just wanted to verify so React is intelligent enough not to render children as attributes. Since although we are getting the attributes from {otherProps} the children props didn't get set
Hey Graymath technology
So, React excludes the children props of a component from props
field by default?
Cheers!
What I meant was that the code below
<button classname="{`btn" ${classname}`}="" {...otherprops}="">{props.children}</button>
when I inspect this section in browser I don't see any of the child attributes populated in front of the class className attribute. But if I check otherProps in console I can see the children key is set on it
Ohh, so as you said, React detects that you are passing as an attribute the "children" property and it just ignores it. Right? :)
What would best practices be for source control for my re-usable components? Lets say I want to package my components so they could be used in other projects and by other developers?
Hey Amy anuszewski!
That's a great question! And when you start wanting to allow your components to be re-usable, several things change :). Most importantly, if you want to share your components, they're should be distributed as an node package. Npm - the registry node packages - does have paid accounts for private packages.
Typically, this also means that the components you want to reuse will need to live in their own git repository. However, I don't believe that's *required*. Especially when you first start developing something that will be reused later, it's a pain to store them in a separate repository, because you're probably hacking quickly on both those components and the first application that will use them. Having 2 separate repositories is clean, but can cause that initial building to slow things down. So, with npm, another option is that you *can* continue to store your reusable package inside your project... just in their own directory. When you publish your npm package (out of scope here, but I know a few things about this), you can literally just publish that *one* directory to npm. Suddenly, you only have 1 repository, but you have successfully published one of its directories as a private npm package. Other developers would install & use your package like normal.
If you have any other questions, I'd be happy to help answer them :).
Cheers!
// composer.json
{
"require": {
"php": "^7.2.0",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^1.6", // 1.9.1
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.3
"doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
"doctrine/orm": "^2.5", // v2.7.2
"friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
"friendsofsymfony/user-bundle": "dev-master#4125505ba6eba82ddf944378a3d636081c06da0c", // dev-master
"sensio/framework-extra-bundle": "^5.1", // v5.2.0
"symfony/asset": "^4.0", // v4.1.4
"symfony/console": "^4.0", // v4.1.4
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.4
"symfony/framework-bundle": "^4.0", // v4.1.4
"symfony/lts": "^4@dev", // dev-master
"symfony/monolog-bundle": "^3.1", // v3.3.0
"symfony/polyfill-apcu": "^1.0", // v1.9.0
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/swiftmailer-bundle": "^3.1", // v3.2.3
"symfony/twig-bundle": "^4.0", // v4.1.4
"symfony/validator": "^4.0", // v4.1.4
"symfony/yaml": "^4.0", // v4.1.4
"twig/twig": "2.10.*" // v2.10.0
},
"require-dev": {
"symfony/debug-pack": "^1.0", // v1.0.6
"symfony/dotenv": "^4.0", // v4.1.4
"symfony/maker-bundle": "^1.5", // v1.5.0
"symfony/phpunit-bridge": "^4.0", // v4.1.4
"symfony/web-server-bundle": "^4.0" // v4.1.4
}
}
// package.json
{
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
},
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.5
"@symfony/webpack-encore": "^0.26.0", // 0.26.0
"babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
"babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
"bootstrap": "3", // 3.3.7
"copy-webpack-plugin": "^4.4.1", // 4.5.1
"core-js": "2", // 1.2.7
"eslint": "^4.19.1", // 4.19.1
"eslint-plugin-react": "^7.8.2", // 7.8.2
"font-awesome": "4", // 4.7.0
"jquery": "^3.3.1", // 3.3.1
"promise-polyfill": "^8.0.0", // 8.0.0
"prop-types": "^15.6.1", // 15.6.1
"react": "^16.3.2", // 16.4.0
"react-dom": "^16.3.2", // 16.4.0
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^7.0.0", // 7.3.1
"sweetalert2": "^7.11.0", // 7.22.0
"uuid": "^3.2.1", // 3.4.0
"webpack-notifier": "^1.5.1", // 1.6.0
"whatwg-fetch": "^2.0.4" // 2.0.4
}
}
Hey Guys,
Is there a way to deal with phpstorm and the
ESLint: 'children' is missing in props validation
?