Custom PropType validation with React (part 1 - a basic checker)
React offers many features to assist developers, including a great suite of validators for checking the props set for a component are as expected. The full details are available in the React Documentation but the core library covers most bases. You can validate whether a prop:
- is required
- contains a primitive type
- contains something renderable (a node)
- is a React Element
- contains one of several defined types
- is an array containing only items of a specified type
- contains an
instanceof
a class - contains an object that has a specific shape
- ...
For example, here is a basic example that will warn (in debug mode) if the wrong type of data is passed in or if data is missing for both title
and content
:
class Widget extends React.Component {
render () {
return (
<div className="widget">
<h1 ref="title" className="widget__title">{this.props.title}</h1>
<div ref="content" className="widget__content">{this.props.content}</div>
</div>
)
}
}
Widget.propTypes = {
title: React.PropTypes.string.isRequired,
content: React.PropTypes.node.isRequired
}
Now if we miss off either title
or content
React will emit an error to the console (using console.warn
) but won't break your app - very handy!
Under the hood
What's actually happening here is very straightforward. Each of the React.PropTypes.{foo}
properties are actually functions that know how to check if a prop is valid or not, returning null
if everything is ok and an Error
if not.
In the implementation for
checkPropTypes
you can see that the React team have also allowed the validator to throw an exception if validation fails though I would avoid this if at all possible and stick to returning anError
ornull
when writing a custom validator
At render time, React calls the function checkPropTypes
for each component to be rendered, passing in the componentName
, props
, propTypes
and location
.
checkPropTypes
iterates over the supplied propTypes
, calling each function in turn with the relevant props
, propName
, componentName
and location
. If the returned value is of type Error
or if the function throws an exception, the error's message is written out to the console via React's warning
function (using console.warn
).
error = propTypes[propName](props, propName, componentName, location);
Writing a custom validator
Sometimes you might need to go beyond the built-in validation functions and because we're working with functions, it's actually remarkably easy to create our own.
By way of an example, we're going to write a validator that will warn if our title is longer than 140 characters.
function tweetLength(props, propName, componentName) {
componentName = comopnentName || 'ANONYMOUS';
if (props[propName]) {
let value = props[propName];
if (typeof value === 'string') {
return value.length <= 140 ? null : new Error(propName + ' in ' + componentName + " is longer than 140 characters");
}
}
// assume all ok
return null;
}
We can now use this function in place of React.PropTypes.string
:
Widget.propTypes = {
title: tweetLength,
content: React.PropTypes.node.isRequired
}
Is this prop required?
The above function will work fine, but what if we want to also make title
a required value? Looking again at the React implementation, we can see that they implement the concept of chained validators with some clever use of bind()
.
function createChainableTypeChecker(validate) {
function checkType(isRequired, props, propName, componentName, location) {
componentName = componentName || ANONYMOUS;
if (props[propName] == null) {
var locationName = ReactPropTypeLocationNames[location];
if (isRequired) {
return new Error(
("Required " + locationName + " `" + propName + "` was not specified in ") +
("`" + componentName + "`.")
);
}
return null;
} else {
return validate(props, propName, componentName, location);
}
}
var chainedCheckType = checkType.bind(null, false);
chainedCheckType.isRequired = checkType.bind(null, true);
return chainedCheckType;
}
The check to see if a value is present if declared as required takes precedence, with an Error being returned if nothing exists in the props
object.
Unfortunately, the createChainableTypeChecker
function isn't exported as part of the PropTypes module so we'll need to write our own version. That's actually quite handy as getting to know this sort of technique is useful if you later want to chain other type checks.
Final Implemenation
You can see the final implementation for this basic checker below, it includes the isRequired
chaining so you can see how this can be integrated.
See it in action on JSBin - http://jsbin.com/sovozi/2/edit?js,console,output
Wrapping up
That's it for part 1, in part 2 we'll look at how make a slightly more complicated validator that takes in options and has a different check on isRequired
and part 3 will cover how we can use PropTypes in our test suite.
If you have any questions or comments about this approach, tweet me - @anatomic
Prop image by Lee Cannon https://flic.kr/p/atkJMh