At least one required prop in React

A more concise version of @finalfreq's solution:

const requiredPropsCheck = (props, propName, componentName) => {
  if (!props.data && !props.url) {
    return new Error(`One of 'data' or 'url' is required by '${componentName}' component.`)
  }
}

Markdown.propTypes = {
  data: requiredPropsCheck,
  url: requiredPropsCheck,
}

Adding on top of finalfreq answer and relating to kousha comment to it.

I had a button component that required either icon or title. Make sure at least one of the is there like in the above answer, after that check its type can be validated like so:

Button.propTypes = {
  icon: (props, propName, componentName) => {
    if (!props.icon && !props.title) {
      return new Error(`One of props 'icon' or 'title' was not specified in '${componentName}'.`)
    }
    if (props.icon) {
      PropTypes.checkPropTypes({
        icon: PropTypes.string, // or any other PropTypes you want
      },
      { icon: props.icon },
      'prop',
      'PrimaryButtonWithoutTheme')
    }
    return null
  }
  title: // same process
}

For more info about PropTypes.checkPropTypes read here


PropTypes actually can take a custom function as an argument so you could do something like this:

MyComponent.propTypes = {
  data: (props, propName, componentName) => {
    if (!props.data && !props.url) {
      return new Error(`One of props 'data' or 'url' was not specified in '${componentName}'.`);
    }
  },

  url: (props, propName, componentName) => {
    if (!props.data && !props.url) {
      return new Error(`One of props 'url' or 'data' was not specified in '${componentName}'.`);
    }
  }
}

which allows for customer Error messaging. You can only return null or an Error when using this method

You can find more info on that here

https://facebook.github.io/react/docs/typechecking-with-proptypes.html#react.proptypes

from the react 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(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

I wrote this helper to solve the same problem in a re-usable way. You use it like a propType function:

MyComponent.propTypes = {
  normalProp: PropType.string.isRequired,

  foo: requireOneOf({
    foo: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]),
    bar: PropTypes.string,
  }, true),
};

and in this example it ensures one of foo or bar is in the MyComponent props. If you leave out the second argument it'll ensure only one of foo or bar is passed.

/**
 * Takes a propTypes object ensuring that at least one of the passed types
 * exists on the component.
 *
 * Usage:
 *
 * MyComponent.propTypes = {
 *   normalProp: PropType.string.isRequired,
 *
 *   foo: requireOneOf({
 *     foo: PropTypes.oneOfType([
 *       PropTypes.string,
 *       PropTypes.number
 *     ]),
 *     bar: PropTypes.string,
 *   }, true),
 * };
 *
 * @param requiredProps object
 * @param allowMultiple bool = false  If true multiple props may be
 *                                    passed to the component
 * @return {Function(props, propName, componentName, location)}
 */
export const requireOneOf = (requiredProps, allowMultiple = false) => {
  return (props, propName, componentName, location) => {
    let found = false;

    for (let requiredPropName in requiredProps) {
      if (requiredProps.hasOwnProperty(requiredPropName)) {
        // Does the prop exist?
        if (props[requiredPropName] !== undefined) {
          if (!allowMultiple && found) {
            return new Error(
              `Props ${found} and ${requiredPropName} were both passed to ${componentName}`
            );
          }

          const singleRequiredProp = {};
          singleRequiredProp[requiredPropName] = requiredProps[requiredPropName];
          const singleProp = {};
          singleProp[requiredPropName] = props[requiredPropName];

          // Does the prop match the type?
          try {
            PropTypes.checkPropTypes(singleRequiredProp, singleProp, location, componentName);
          } catch (e) {
            return e;
          }
          found = requiredPropName;
        }
      }
    }

    if (found === false) {
      const propNames = Object.keys(requiredProps).join('", "');
      return new Error(
        `One of "${propNames}" is required in ${componentName}`
      );
    }
  };
};