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 SubscribeLook at RepLogList
: it uses 2 props: highlightedRowId
and onRowClick
. But, what guarantees that RepLogList
is actually passed these props? What prevents us from forgetting to pass those two exact props from RepLogs
?
The answer is... absolutely nothing! In PHP, when you instantiate an object, if that object has a constructor with required arguments, we are forced to pass those arguments. But, with React components, it's the wild west! There is nothing like that. There is simply no guarantee that we are passed any of these props. There's also no way for a component to easily document, or advertise what props it needs.
This is an especially big problem with onRowClick
, because, if we forget to pass this prop, our whole app will break when someone clicks on a row.
To fix this, React leverages something called "prop types", which uses an external library. Find your terminal and install it:
yarn add prop-types --dev
And... ding! With prop types, we can tell React exactly which props a component needs, and even what they should look like. At the bottom of the component, add RepLogList.propTypes =
and pass it an object. This is where you describe all the different props this component might have. But first, back on top, import PropTypes
from prop-types
. And... back down below, add highlightedRowId
set to PropTypes.any
. Then, onRowClick
set to PropTypes.func
.
... line 1 | |
import PropTypes from 'prop-types'; | |
... lines 3 - 30 | |
RepLogList.propTypes = { | |
highlightedRowId: PropTypes.any, | |
onRowClick: PropTypes.func | |
}; |
For highlightedRowId
, we could have used PropTypes.number
, because, it is a number right now. But later, we're going to refactor our ids to be uuid's, which are a string.
Let's see this in action! Go back to RepLogs
and, instead of passing in a function, be devious: pass a string!
Check it out: move back to your browser! An error! Oh, and, notice: the page refreshed automatically before we got here. That's thanks to the Encore dev-server
we're running: when we save a file, our browser automatically refreshes, which, is kinda nice.
Anyways, we see:
Invalid prop
onRowClick
of type string supplied toRepLogList
, expected function.
This is what PropTypes give you: clear & early warnings when we mess up. I mess up a lot!
The highlightedRowId
prop in RepLogList
is technically an optional prop: if we forget to pass it... no problem! No rows are highlighted. But the onRowClick
prop... that's a different story: if we forget this, boom! Our code will explode in grand fashion when the user clicks a row.
By default, all propTypes are optional. To make one required, just add .isSuperDuperImportant
. I'm kidding, add .isRequired
. But, I feel like my name would have been much more awesome.
... lines 1 - 30 | |
RepLogList.propTypes = { | |
... line 32 | |
onRowClick: PropTypes.func.isRequired | |
}; |
Back in RepLogs
, let's mess with stuff! "Forget": to pass that prop entirely. Move to your browser and... yep!
The prop onRowClick is marked as required... but its value is undefined.
I love it! Enough fun: let's re-add the prop. Woh! We're now getting auto-complete!! Yeaaa! PhpStorm reads the propTypes
and uses them to help us out.
And, in RepLogList
, before, we had big red warnings when we referenced our props. That came from ESLint: it was telling us that we forgot to add the prop types. Now, everyone is happy.
Because propTypes are amazing, let's add them everywhere else, like in RepLogApp
. Here, we're relying on a withHeart
prop. And, there is the warning I was just talking about:
Missing props validation
You guys know the drill! First, import PropTypes
from prop-types
. Then, at the bottom, RepLogApp.propTypes =
an object with withHeart
set to PropTypes.bool
. The prop isn't really required, so I'll leave it optional.
... lines 1 - 2 | |
import PropTypes from 'prop-types'; | |
... lines 4 - 33 | |
RepLogApp.propTypes = { | |
withHeart: PropTypes.bool | |
} |
The last place we need propTypes is in RepLogs
: we depend on 3. Go copy the import statement from RepLogApp
, and paste! At the bottom, add the RepLogs.propTypes =
part.
... lines 1 - 2 | |
import PropTypes from 'prop-types'; | |
... lines 4 - 78 | |
RepLogs.propTypes = { | |
... lines 80 - 82 | |
}; |
The 3 props are: withHeart
, highlightedRowId
and onRowClick
. Steal withHeart
from RepLogApp
and paste. Then, more stealing! Get the other two from RepLogList
and put those here too.
... lines 1 - 78 | |
RepLogs.propTypes = { | |
withHeart: PropTypes.bool, | |
highlightedRowId: PropTypes.any, | |
onRowClick: PropTypes.func.isRequired | |
}; |
Hmm, this shows off another common thing in React. Frequently, you'll pass props into one component, just so that it can pass them into another component. For example, in RepLogApp
, we pass 3 props. But, two of them aren't event used in RepLogs
! We just pass them straight to RepLogList
!
This "props passing" can be kind of annoying. But, it's not necessarily a sign of bad design. It's just part of using React. There are ways to organize our code to help this, but many are more advanced. The point is: this is ok.
Next, let's make a small production optimization with prop types.
"Houston: no signs of life"
Start the conversation!
// 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
}
}