Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Add ComponentOfType] #146

Closed
wants to merge 6 commits into from
Closed

[Add ComponentOfType] #146

wants to merge 6 commits into from

Conversation

congwenma
Copy link

@congwenma congwenma commented Dec 6, 2017

Adds ComponentOfType propType checker.

This helps check Component's type on the receiving end.

//  1. single expected
  propType = {
    label: elementOfType('label')
  }

// 2. multiple expected, multiple given
  propType = {
    children: elementOfType([ MyListItem, 'label' ])
  }

  propType = {
    children: elementOfType([ ...SVG_TAG_GROUP, MyCustomTag ])
  }

@ljharb
Copy link
Collaborator

ljharb commented Dec 6, 2017

Technically this is an element, not a component - it's similar to elementType in https://www.npmjs.com/package/airbnb-prop-types.

README.md Outdated
@@ -89,11 +91,14 @@ MyComponent.propTypes = {
// it as an enum.
optionalEnum: PropTypes.oneOf(['News', 'Photos']),

optionalElementOfType: PropTypes.elementOfType(['div', OtherComponent]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why allow it to take an array of types instead of a single one? PropTypes.oneOfType is the established way to compose multiple validators.

Separately, I think "oneOf" is always in the name when it's taking an array of multiples; thus, I think elementOfType taking one, or oneOfElementType taking an array, is what would make sense - and I think the latter sounds silly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specific problem I'm trying to solve is the oneOfType scenario, but considering the need to whitelist more than one type of components:

<CustomTable>
  <CustomHeader/>
  <CustomTableRow/>
  <CustomTableRow/>
  <CustomTableRow/>
  <CustomFooter/>
</CustomTable>

// this is what I'm looking for
CustomTable.propTypes = {
  children: elementsOfType([CustomHeader, CustomTableRow, CustomFooter])
}

or

<SVGSection>
  <use/>
  <g></g>
  <rectangle/>
  <CustomSvgLine />
</SVGSection>

SVGSection.propTypes = {
  children: elementsOfType([ ...SVG_CHILDREN, ...MY_CUSTOM_SVG_CHILDREN ])
}

I'm hoping to capture these use cases so that when allow reusability of component, I'm always covered by this type checker.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PropTypes.oneOfType([ ...SVG_CHILDREN, ...MY_CUSTOM_SVG_CHILDREN ].map(x => PropType.elementOfType(x))) works just fine, without overcomplicating the propType validator.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb that sounds reasonable, I will make the change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb fyi, one feature we will lose from this change is a error message that says expected one of type [div, span, CustomComponent], if we use oneOfType, we will only be seeing the generic message Invalid prop supplied ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there’s a way oneOfType can read info off of the validators it receives to improve the error message, but I’m not sure what it is.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb i wonder how that would work if each proptype returns a different error message.

}

function validate(props, propName, componentName, location, propFullName) {
expectedTypes = [].concat(expectedTypes)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should throw if expectedTypes isn't an array (if it takes an array), like oneOf/oneOfType do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm hoping to give user some flexibility in case they only want to pass a single expected element type.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flexibility in APIs leads to bugs; if they want to pass a single type, they can add [ ] without difficulty.

@@ -871,6 +871,82 @@ describe('PropTypesDevelopmentStandalone', () => {
});
});

describe('ElementOfType Types', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ‘of type” implies it receives propType validators; i think this should be called “elementOf”.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are really checking if the given object has a .type attribute that matches the given accepted, hows ElementWithType

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe just elementType?

Copy link
Author

@congwenma congwenma Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, there's already a createElementTypeChecker which is a little bit confusing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createElementTypeTypeChecker :-D

Copy link
Collaborator

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case that fails with { type: 'div' }? In other words, React.isValidElement should always be true in order for this validator to pass.

var ACCEPTABLE_TYPES_OF_EXPECTED_TYPES = ['string', 'function'];

if (ACCEPTABLE_TYPES_OF_EXPECTED_TYPES.indexOf(typeof expectedType) === -1) {
process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to ElementWithType, expected an Html tag name or a Component.') : void 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not use warning here; I'm suggesting this have a literal throw statement.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be different from the behaviors of other type checkers.

https://github.com/congwenma/prop-types/blob/add-componentOfType/factoryWithTypeCheckers.js#L277

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, that seems like a missed opportunity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this be a useful pattern by not making a big deal about prop types in production?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean; invalid props are bugs. The point of not warning about them in production is performance; the exception I'm hoping for here would only apply to dev anyways, since the entire propType validator is compiled out anyways in production.

Copy link
Author

@congwenma congwenma Dec 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I thought you mean replacing the entire line with a throw.
I think what you are suggesting can make things cleaner and easier, plus we'd be removing dependency on fbjs/warning, however I think theres some logic in fbjs/warning that helps debugging prop-types and handle some special cases, and this probably warrants a separate PR.

@ljharb
Copy link
Collaborator

ljharb commented Feb 28, 2019

@congwenma would you mind rebasing this, and addressing outstanding comments?

@ljharb
Copy link
Collaborator

ljharb commented Mar 7, 2019

@congwenma #146 (comment) in particular

factoryWithTypeCheckers.js Outdated Show resolved Hide resolved
factoryWithTypeCheckers.js Outdated Show resolved Hide resolved
factoryWithTypeCheckers.js Outdated Show resolved Hide resolved
}

function validate(props, propName, componentName, location, propFullName) {
var propValues = [].concat(props[propName]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should just be props[propName]; there's no reason to support multiple values here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying the support of multiple element types should be using oneOf?

Copy link
Collaborator

@ljharb ljharb Mar 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oneOfType, yes.

factoryWithTypeCheckers.js Outdated Show resolved Hide resolved
ljharb and others added 4 commits March 7, 2019 09:06
Co-Authored-By: congwenma <congwen.ma@gmail.com>
Co-Authored-By: congwenma <congwen.ma@gmail.com>
Co-Authored-By: congwenma <congwen.ma@gmail.com>
Co-Authored-By: congwenma <congwen.ma@gmail.com>
@@ -51,6 +51,7 @@ module.exports = function() {
objectOf: getShim,
oneOf: getShim,
oneOfType: getShim,
elementWithType: getShim,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also let’s name this elementType

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elementType already exist, looks like this PR beated us to it. b67bbd4.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, indeed - looks like #211 may have replaced this.

@ljharb
Copy link
Collaborator

ljharb commented Apr 1, 2019

Replaced with #211.

Thanks for the contribution!

@ljharb ljharb closed this Apr 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants