No time for “welcomes” or even “welcome backs” now! Baton down the hatches! We’re going to dive deep into the depths of the React source this week and take a look at PropTypes and what makes them tick. Don’t fret if you’re only just learning about PropTypes we’re going to take a quick tour of them before we begin our decent. So strap in as we get delving into the deep dark grok of React!

 

Whoa, slow down egghead?! PropTypes?

Just in case this is your first time coming across the concept of PropTypes allow me to give a quick introduction to the concept. To put it simply PropTypes allow you to specify which types should be used by the values of the props that are passed into your components.

PropTypes can come in a number of forms. These range from simply checking if a prop is of a certain primitive type to checking if the prop contains an object with a certain shape that you have specified. You can even define your own custom PropTypes right there in your components if there’s something specific to your app that you want to account for. This is done by adding a function with a certain signature as the value of one of your propType properties.

But then, you might be wondering why we even care about the types being passed into our components. It’s javascript right? Types?! Ain’t nobody got time fo’ that! Well, there are a number of reasons why you will find it valuable to define the types that your component accepts.

Firstly, it helps you to facilitate a good componentised UI pattern because it means that you can code your components in a way in which they completely trust the data that is being handed them. Of course, I don’t mean trust in terms of security what I mean is, for example, if your component specifies that an value must come in as a number then the component can just start operating on it as a number. There’s no need to check if there is a need to parse the number out from a string or anything like that.

In this way the component gains some decoupling from it’s parents because it knows what to expect from them and doesn’t have to write code to address a poorly behaving parent.

Another big benefit of having PropTypes is that they exist almost as living documentation of how to use the component. Even if your colleague wrote the component 6 months ago and no one remembers exactly how it works; at least you have a starting point in that, in theory, you’ll be able to give it props that will make it work. The best part is that your component now has a  list of props and how to use them written right there into the component in code. That code will be put through your build pipeline, linted and transpiled etc. so you can worry less about getting things wrong and let the computer do that work for you.

 

PropTypes primer, promptly please?

Let’s take a quick look at how you might use PropTypes in a component designed to display a user’s name, and if they’re so bold (and young) display their age as well. The code to define something like this might look like this:

import React, { PropTypes } from 'react';
const InfoDisplay = (props) => {
<div>
<div>{this.props.name}</div>
<div>
{
this.props.displayAge
&& this.props.age
}
</div>
</div>
};
InfoDisplay.propTypes = {
name: PropTypes.string.isRequired,
displayAge: PropTypes.bool,
age: PropTypes.number,
}

What I’ve highlighted here is a pretty standard usage of the PropTypes functionality. We’re declaring that when you use this component that you MUST pass in a prop called name and it must be of type string. Also, we can optionally send in an age along with a flag to say if we should display that value of age. Neither of these props have to be supplied but if they are they must be of type number and bool respectively.

When running your application in a dev environment, your browser’s dev console would be getting logged to telling you that you’ve failed to meet these criteria. In our example, if you managed to fumble code that uses an InfoDisplay and pass it the wrong props or forget to supply a name that’s when React would log an error for you.

It needs to be clarified though that react only logs the error in the console. It doesn’t throw an exception, it logs an error. The execution of the react internals don’t halt. So unless, in contravening your own PropType rules, your component causes a javascript exception then your only clue that you’ve done anything wrong is in the console.

In our example above , if you were to forget to pass in the name but still passed in values for the displayAge and age props then the component would still faithfully display the age on the screen without the name and you’re only clue that something went wrong, other than the display quirk, would be the error logged to the console.

 

What I’m saying is that if the types you’re using in your components is something you feel is important and you want to introduce PropTypes as a form of protection then the onus will be on you to keep an eye on your console to ensure you’re staying true to your own rules.

 

So that is a basic usage of PropTypes, I’ve found that the documentation on this functionality is useful. If you want to know the ins and outs of just how to use PropTypes then I’d encourage you to check out the docs.

 

Roll up your sleeves, it’s about to get……codey

So, without further ado, let’s go deep. You’ll find the code that runs the propTypes functionality in src/isomorphic/classic/types/ReactPropTypes.js . If you scan down this file you’ll find the first significant piece of code, as far as the functionality is concerned, on line 73. I’ve listed it  below for your convenience:

if (__DEV__) {
// Keep in sync with production version below
ReactPropTypes = {
array: createPrimitiveTypeChecker('array'),
bool: createPrimitiveTypeChecker('boolean'),
func: createPrimitiveTypeChecker('function'),
number: createPrimitiveTypeChecker('number'),
object: createPrimitiveTypeChecker('object'),
string: createPrimitiveTypeChecker('string'),
symbol: createPrimitiveTypeChecker('symbol'),
any: createAnyTypeChecker(),
arrayOf: createArrayOfTypeChecker,
element: createElementTypeChecker(),
instanceOf: createInstanceTypeChecker,
node: createNodeChecker(),
objectOf: createObjectOfTypeChecker,
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,
shape: createShapeTypeChecker,
};
} else {
var productionTypeChecker = function() {
invariant(
false,
'React.PropTypes type checking code is stripped in production.'
);
};
productionTypeChecker.isRequired = productionTypeChecker;
var getProductionTypeChecker = () => productionTypeChecker;
// Keep in sync with development version above
ReactPropTypes = {
array: productionTypeChecker,
bool: productionTypeChecker,
func: productionTypeChecker,
number: productionTypeChecker,
object: productionTypeChecker,
string: productionTypeChecker,
symbol: productionTypeChecker,
any: productionTypeChecker,
arrayOf: getProductionTypeChecker,
element: productionTypeChecker,
instanceOf: getProductionTypeChecker,
node: productionTypeChecker,
objectOf: getProductionTypeChecker,
oneOf: getProductionTypeChecker,
oneOfType: getProductionTypeChecker,
shape: getProductionTypeChecker,
};
}

You can see here that what react is doing is defining the ReactPropTypes object. This object later gets exported from this module for use in React.

You’ll notice that the code defines the object differently based on whether or not a global property called __DEV__ is set. What this suggests is that PropTypes act differently based on whether they’re being used in development or production. Curious.

Let’s first inspect what happens in production. You can see what we do first in the production branch of this if statement is define a variable called productionTypeChecker which we populate with a function and this then gets used in the different properties of the ReactPropTypes object that will be later exported.

So what is happening here? Well if you look inside the productionTypeChecker function you can see that it is calling a function called invariant which was required in at the top of the file.

Invariant is used in the react code base to provide react developers with a standard function to call that will always throw an error. This error is populated with a sensible message including links to appropriate supporting documentation if there are any. How this works is outside of the scope of this conversion but if you want to see how invariant is implemented then you can check out src/shared/utils/reactProdInvariant.js .

After taking all this on board it should be obvious then that this code’s purpose is to safe guard against Eye-Dee-Ten-Tee errors (ID10T). It’s basically to try and save developers who are doing silly things like calling propType functions directly. In production React itself isn’t going to a call these PropType functions because React knows that PropTypes are a “development only” concept. So then if anyone else is calling these in a production build we want them to feel appropriately silly and give them an error message reminding them what silly sods they’ve been.

 

D is for Dev

So now that we understand that PropTypes are a concept meant purely for guidance during development time and they are not a “run time in production” concept, let’s look at exactly what’s going on when we are in development. At a glance it looks like createPrimitiveTypeChecker gets used to define most checkers here so let’s look at that:

function createPrimitiveTypeChecker(expectedType) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== expectedType) {
var locationName = ReactPropTypeLocationNames[location];
// `propValue` being instance of, say, date/regexp, pass the 'object'
// check, but we can offer a more precise error message here rather than
// 'of type `object`'.
var preciseType = getPreciseType(propValue);
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
`\`${expectedType}\`.`
);
}
return null;
}
return createChainableTypeChecker(validate);
}

You can see that it begins with the definition of a function called validate. Based on the signature of the validate function we can guess that validate will access the appropriate prop that needs to be validated by using the value of props[propName].

We can then pass the value of the prop into a function called getPropType which is a funky little function that helps us to determine the exact type the prop we’re validating with a little bit more accuracy than just using the built in typeof syntax available in javascript.

If you’re interested in what I mean by this extra accuracy feel free to take a look for yourself, the code for this is on line 481:

// Equivalent of `typeof` but with special handling for array and regexp.
function getPropType(propValue) {
var propType = typeof propValue;
if (Array.isArray(propValue)) {
return 'array';
}
if (propValue instanceof RegExp) {
// Old webkits (at least until Android 4.0) return 'function' rather than
// 'object' for typeof a RegExp. We'll normalize this here so that /bla/
// passes PropTypes.object.
return 'object';
}
if (isSymbol(propType, propValue)) {
return 'symbol';
}
return propType;
}

Once we’ve determined the exact type it’s then a simple matter of checking if the type of data we currently have matches the name of the datatype that we were expecting. Remember, this expected datatype is passed into the createPrimitiveTypeChecker function when we create it in the definition of the proptypes object in the first code snippet above.

If this test fails then a message is built and passed into a PropTypeError object which is returned; ready to be logged into the console by the caller of this code or one of it’s parents.

After the definition of the validate function it is passed into the createChainableTypeChecker function the result of which is returned from createPrimitiveTypeChecker.

I’ll let you take a look at createChainableTypeChecker in your own time but in a nutshell all this function does is bootstrap the validate function to allow use to use the fluid syntax that you saw available to us when we were using propTypes at the start of this blog post. Like this:

name: PropTypes.string.isRequired,

 

Coming back up for air

We’ve gone pretty deep into the internals of React here so let’s come back up to the surface and summarise what you should be taking away as having learned from all this.

  1. When you use propTypes you are defining a property of type object on your React components.
  2. Then in a development build the component will notice that it has this propTypes property on it. It will take this propTypes property and iterate over the properties of the object.
  3. For each of the properties it finds on the propTypes object it is able to use the key to work out the name of the prop that needs validating.
  4. From there the component can take the value of the property (which we now know is actually a validation function) and execute it, passing in the props that were passed in and the propName of the prop we’re validating. Also passed in will be the location and propFullName parameters, both of which are just used for the error message if the validation fails.

So what this all means is that when you define a propTypes object with the syntax shown in our InfoDisplay component above, really you’re just defining which built in validation functions are going to be executed by the react internals.

When you think about it this way the functionality where you can create your own custom validators as per the docs makes perfect sense. You’re just defining another type of validator function. Just like React has it’s internal validators you can also define your own. These custom validators will then go into the same validation pipeline to be fired in exactly the same way.

Well, that was a deep dive into the internals of the React PropTypes functionality. If I haven’t been clear at any point please don’t hesitate to leave a comment or contact me directly on any of the social networks I have linked at the top of the page.

I hope you enjoyed this article and I look forward to providing you with more deep dive material in the near future.