facebook / prop-types

Runtime type checking for React props and similar objects
MIT License
4.48k stars 356 forks source link

Add ability to override `isRequired` #339

Closed dkreft closed 3 years ago

dkreft commented 3 years ago

When passing props from one component to another, I like to have a single source of truth for my propTypes--the component that actually uses the prop:

// File: A.jsx

import PropTypes from 'prop-types'

import B from './B'

export default function A({ className, foo }) {
  return (
    <div className={ className }>
      <B foo={ foo } />
    </div>
  )
}

A.propTypes = {
  className: PropTypes.string,
  foo: B.propTypes.foo,
}
// File: B.jsx

import PropTypes from 'prop-types'

export default function B({ foo }) {
  return ( /* blah blah blah */ )
}

B.propTypes = {
  foo: PropTypes.string.isRequired,
}

In this case, the foo property on A is mandatory...which is great. Well, that is until I want to make foo optional on A because I'm declaring a default value for it:

A.propTypes = {
  className: PropTypes.string,
  // Warnings ensue...
  foo: B.propTypes.foo,
}

A.defaultProps = {
  foo: 'default value provided by A',
}

What I'd like to be able to do is override the isRequired flag, perhaps like this:

A.propTypes = {
  className: PropTypes.string,
  foo: B.propTypes.foo.optional,   // <=== `optional` overrides `isRequired`
}

If there's some other reasonable way around this problem, I'm all ears.

ljharb commented 3 years ago

If foo is required for B, then it's absolutely required for A in your example. In other words, omitting it on A will be a bug when it gets to B and isn't present, so it should be required for both.

Do you have another example?

dkreft commented 3 years ago

@ljharb, I think you may have missed A.defaultProps in the third code block:

Screen Shot 2021-03-18 at 2 20 21 PM

In that case, it is optional for A by virtue of the defaultProps, but of course, prop-types still recognizes it as a mandatory property in A because there's no way to override the isRequired...which is the issue that I'm seeking to resolve.

ljharb commented 3 years ago

I could still pass foo as null and it would break, because it should, in fact, be required on A.

dkreft commented 3 years ago

I feel like maybe we're speaking past each other.

I'm trying to figure out a way to make the param mandatory for B but optional for A, but it sounds like you're talking about the status quo.

If you have a recommendation for how to achieve this, I'm all ears.

ljharb commented 3 years ago

This is how:

A.propTypes = {
  foo: B.propTypes.foo, // required
};
A.defaultProps = {
  foo: 'some default'
};

This way, foo is required, but because it is also defaulted, the user doesn't have to pass it - but if they pass null it's invalid, which is correct.

dkreft commented 3 years ago

Hrmm. Well, then...I suppose I'll just crawl back under the rock out from which I came.

image