Multiple validations on React PropTypes

PhilVarg picture PhilVarg · Jul 1, 2015 · Viewed 8.1k times · Source

Is there a way to have multiple validations on a single prop using React.PropTypes. specifically want to blend a custom validation and a stock validation.

I have two props, an object options, and a string value. i want to check that props.value is a string but also a key on the object. using coffeescript this looks like:

propTypes:
  options: React.PropTypes.Object.isRequired
  value: (props, propName, componentName) ->
    unless props[propName] of props.options
      new Error('my custom message')

this works great, but i also want to ensure that my value is a string type. I'm sure i can manually stick in that validation in the custom function no problem, but ideally, i'd just like to use React.PropTypes.string.isRequired. ive tried just putting it in the custom function and executing it but that did not work. the following didn't work either:

  value: React.PropTypes.string.isRequired && (props, propName, componentName) ->
    unless props[propName] of props.options
      new Error('my custom message')

is there a way to get this working using reacts built in validator, or is rewriting it in my function the only option?

Answer

Brigand picture Brigand · Jul 1, 2015

From the Reusable Components page in the docs:

You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.
customProp: function(props, propName, componentName) {
  if (!/matchme/.test(props[propName])) {
    return new Error('Validation failed!');
  }
}

So a propType returns nothing or an error object. We can write a 'all' function which takes two propTypes and merges the result.

const allPropTypes = (...types) => (...args) => {
  const errors = types.map((type) => type(...args)).filter(Boolean);

  // no errors? cool!
  if (errors.length === 0) return;

  // collect the messages and join them together
  const message = errors.map((e) => e.message).join('\n');
  return new Error(message);
};

Then you can use this to assert against multiple propTypes.

propTypes = {
  foo: allPropTypes(
    PropTypes.string.isRequired,
    (props, propName, componentName) => props.options && props.options[props[propName]] 
      ? undefined
      : new Error(
          `${componentName}: expected prop ${propName}="${prop[propName]}"` 
          + `to be a key of prop "options" `
          + `(one of ${Object.keys(props.options).join(', ')})`
        )
  )
}

Note: none of this is tested, but no syntax errors! You can compile it to es3 with babel, or convert it to CS by hand.