ljharb / prop-types-tools

Custom React PropType validators
MIT License
671 stars 50 forks source link

mutually exclusively require prop #53

Open sygint opened 5 years ago

sygint commented 5 years ago

I have a component and it can take a prop of 'src' or children, but never both. I was unable to find a proptype supplied in this library to cover this.

@ljharb was helping me compose a prop-type for this and while it does seem to disallow the other prop if one is already provided, it doesn't also require that one exist.

here is the proposed prop-type:

static propTypes = { src: disallowedIf(string, "children", any.isRequired), children: disallowedIf( oneOfType([arrayOf(node), node]), "src", any.isRequired ) };

I am happy to contribute a solution for a mutulallyExclusivelyRequired or something similar if needs be.

Thanks!

pillowfication commented 5 years ago

I also ran into this problem, and ended up using the following solution:

const exclusivePropTypes = {
  foo: fooValidator,
  bar: barValidator,
  baz: bazValidator
}
const exclusiveProps = Object.keys(exclusivePropTypes)

Component.propTypes = {
  ...Object.fromEntries(exclusiveProps.map(exclusiveProp => [
    exclusiveProp,
    AirbnbPropTypes.and([
      exclusivePropTypes[exclusiveProp],
      (props, propName, componentName, ...rest) => {
        const propList = exclusiveProps.join(', ')
        const exclusivePropCount = Object.keys(props)
          .filter(prop => props[prop] != null)
          .reduce((count, prop) => (count + (exclusivePropTypes[prop] ? 1 : 0)), 0)
        if (exclusivePropCount > 1) {
          return new Error(`A ${componentName} cannot have more than one of these props: ${propList}`)
        }
        if (exclusivePropCount < 1) {
          return new Error(`A ${componentName} must have at least one of these props: ${propList}`)
        }
      }
    ])
  ]))
}
Nantris commented 3 years ago

Ugh... we're just going to undertype things here if that's the alternative.