Putting React Custom PropTypes to Work

Let’s write a component whose basic currency is a list of emails. It could be any other datum that doesn’t map nicely to one of the primitives in React.PropTypes, but–since email validation is always sure to please–we’ll start there. In a perfect world, anyone consuming the component will only ever provide it with valid emails. That’s great news: if we only get what we expect, we won’t have to worry about validating inputs ourselves. Beauty!

Unfortunately, someone out there will misuse our work. They’ll give us a Number, an Object, null–something far enough removed from the email we had hoped for to make the component blow its lid. But React? React wants to help us avoid this. Just by using React’s built-in PropTypes, we can get a bit of quasi-typed sanity around our component’s props.

var EmailList = React.createClass({
  propTypes: {
    emails: React.PropTypes.array.isRequired
  },
  render: function () {
    return React.DOM.ul(null, this.props.emails.map(function (email) {
      return React.DOM.li({ key: email }, email);
    }));
  }
});

var emailListFactory = React.createFactory(EmailList);

Great start! Now, guess what happens when we instantiate it?

React.render(emailListFactory({
  emails: 'not an array'
}), parentEl);

First, there’s a warning:

Warning: Invalid prop `emails` of type `string` supplied to `email-list`, expected an array.

So far, so good. We see that we’re doing something wrong. But then, when the interpreter tries to map over the string, there’s the explosion–method [map] undefined.

Batteries included

PropTypes warnings are an awesome tool for catching bad input before it hits production. To get the most out of them, though, we need to be able to describe what we expect to see in exquisite detail. In the example above, we’ve checked for an Array. Unfortunately, we won’t see a warning when invalid array entries make it through:

React.render(emailListFactory({
  emails: [
    'foo@example.com',
    'foobar'
  ]
}), parentEl);

If we’re limited to React’s primitive PropTypes, we can only assert that the array contents follow a (similarly primitive) type. We can test for an array of strings, for instance:

propTypes: {
  emails: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
}

What we can’t do with native PropTypes is ensure that every string in our list was indeed a reasonably plausible email. What we can do is extend the built-ins with validators that suit our specific use-case.

Leveling up

Say we have a simple test for email addresses. We won’t try to cover all the cases–it only needs to provide reasonable coverage over errant user inputs:

// Terribly simple and easily circumvented
function isEmail (str) {
  return (str instanceof String) && /.*?@.*?\.[a-z]{2,4}/.test(str);
}

In order to apply our test as a PropType validator, we need to wrap it in a function with a very specific signature. Specifically, it needs to take the calling state and return either nothing (tested prop was valid) or an instance of an Error suitable for the invalid prop. For an email address, we might use something like:

var emailPropType = function (props, propName, component) {
  if (!isEmail(props[propName])) {
    return new Error('Invalid email!');
  }
};

That’s really all it is: just a function implementing a very straightforward interface. Now, any components using the emailPropType will warn if they receive invalid addresses:

var EmailList = React.createClass({
  propTypes: {
    emails: React.PropTypes.arrayOf(emailPropType).isRequired
  },
  render: function () {
    return React.DOM.ul(null, this.props.emails.map(function (email) {
      return React.DOM.li({ key: email }, email);
    }));
  }
});

var emailListFactory = React.createFactory(EmailList);

React.render(emailListFactory({
  emails: [
    'foo@example.com',
    'bar' // => 'Warning: Invalid email!'
  ]
}), parentEl);

How-Tos

Practical tutorials and hands-on guides

Read all

Let’s keep in touch

Reach out on Twitter or subscribe for (very) occasional updates.

Hey, I'm RJ: digital entomologist and intermittent micropoet, writing from the beautiful Rose City.