Custom PropType validation with React (part 1 - a basic checker)

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 an Error or null 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.

JS Bin

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