Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
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).

Prop Validation: PropTypes

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Look 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.

Introducing PropTypes

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.

PropTypes Give us Warnings

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 to RepLogList, expected function.

This is what PropTypes give you: clear & early warnings when we mess up. I mess up a lot!

Required PropTypes

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.

PhpStorm ❤️'s PropTypes!

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.

Adding propTypes Everywhere Else

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.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

What PHP libraries does this tutorial use?

// 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
    }
}

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice