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 a PropTypes for React elementType (ie. MyComponent) #211

Merged
merged 1 commit into from Feb 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -78,9 +78,12 @@ MyComponent.propTypes = {
// (or fragment) containing these types.
optionalNode: PropTypes.node,

// A React element.
// A React element (ie. <MyComponent />).
optionalElement: PropTypes.element,

// A React element type (ie. MyComponent).
optionalElementType: PropTypes.elementType,

// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
optionalMessage: PropTypes.instanceOf(Message),
Expand Down
72 changes: 72 additions & 0 deletions __tests__/PropTypesDevelopmentStandalone-test.js
Expand Up @@ -18,6 +18,7 @@ function resetWarningCache() {
React = require('react');
PropTypes = require('../index');
}
resetWarningCache();

function getPropTypeWarningMessage(propTypes, object, componentName) {
if (!console.error.calls) {
Expand Down Expand Up @@ -555,6 +556,77 @@ describe('PropTypesDevelopmentStandalone', () => {
});
});

describe('ElementType Types', () => {
it('should support native component', () => {
typeCheckPass(PropTypes.elementType, 'div');
});

it('should support stateless component', () => {
var MyComponent = () => <div />;
typeCheckPass(PropTypes.elementType, MyComponent);
});

it('should support stateful component', () => {
class MyComponent extends React.Component {
render() {
return <div />;
}
}
typeCheckPass(PropTypes.elementType, MyComponent);
});

(React.forwardRef ? it : it.skip)('should support forwardRef component', () => {
const MyForwardRef = React.forwardRef((props, ref) => <div ref={ref} />);
typeCheckPass(PropTypes.elementType, MyForwardRef);
});

(React.createContext ? it : it.skip)('should support context provider component', () => {
const MyContext = React.createContext('test');
typeCheckPass(PropTypes.elementType, MyContext.Provider);
});

(React.createContext ? it : it.skip)('should support context consumer component', () => {
const MyContext = React.createContext('test');
typeCheckPass(PropTypes.elementType, MyContext.Consumer);
});

it('should warn for invalid types', () => {
typeCheckFail(
PropTypes.elementType,
true,
'Invalid prop `testProp` of type `boolean` supplied to ' +
'`testComponent`, expected a single ReactElement type.',
);

typeCheckFail(
PropTypes.elementType,
{},
'Invalid prop `testProp` of type `object` supplied to ' +
'`testComponent`, expected a single ReactElement type.',
);

typeCheckFail(
PropTypes.elementType,
[],
'Invalid prop `testProp` of type `array` supplied to ' +
'`testComponent`, expected a single ReactElement type.',
);

it('should warn for missing required values', () => {
typeCheckFailRequiredValues(PropTypes.elementType.isRequired);
});
});

it('should warn for React element', () => {
typeCheckFail(
PropTypes.elementType,
<div />,
'Invalid prop `testProp` of type `object` supplied to ' +
'`testComponent`, expected a single ReactElement type.',
);
});
});

describe('Instance Types', () => {
it('should warn for invalid instances', () => {
function Person() {}
Expand Down
7 changes: 7 additions & 0 deletions __tests__/PropTypesProductionStandalone-test.js
Expand Up @@ -162,6 +162,13 @@ describe('PropTypesProductionStandalone', function() {
});
});

describe('React ElementType Type', function() {
it('shoud be a no-op', function() {
expectThrowsInProduction(PropTypes.elementType.isRequired, false);
expectThrowsInProduction(PropTypes.elementType.isRequired, {});
});
});

describe('ObjectOf Type', function() {
it('should be a no-op', function() {
expectThrowsInProduction(PropTypes.objectOf({foo: PropTypes.string}), {
Expand Down
1 change: 1 addition & 0 deletions factoryWithThrowingShims.js
Expand Up @@ -45,6 +45,7 @@ module.exports = function() {
any: shim,
arrayOf: getShim,
element: shim,
elementType: shim,
instanceOf: getShim,
node: shim,
objectOf: getShim,
Expand Down
14 changes: 14 additions & 0 deletions factoryWithTypeCheckers.js
Expand Up @@ -7,6 +7,7 @@

'use strict';

var ReactIs = require('react-is');
var assign = require('object-assign');

var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
Expand Down Expand Up @@ -123,6 +124,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
any: createAnyTypeChecker(),
arrayOf: createArrayOfTypeChecker,
element: createElementTypeChecker(),
elementType: createElementTypeTypeChecker(),
instanceOf: createInstanceTypeChecker,
node: createNodeChecker(),
objectOf: createObjectOfTypeChecker,
Expand Down Expand Up @@ -276,6 +278,18 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
return createChainableTypeChecker(validate);
}

function createElementTypeTypeChecker() {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
if (!ReactIs.isValidElementType(propValue)) {
var propType = getPropType(propValue);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement type.'));
}
return null;
}
return createChainableTypeChecker(validate);
}

function createInstanceTypeChecker(expectedClass) {
function validate(props, propName, componentName, location, propFullName) {
if (!(props[propName] instanceof expectedClass)) {
Expand Down
13 changes: 2 additions & 11 deletions index.js
Expand Up @@ -6,21 +6,12 @@
*/

if (process.env.NODE_ENV !== 'production') {
var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' &&
Symbol.for &&
Symbol.for('react.element')) ||
0xeac7;

var isValidElement = function(object) {
return typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE;
};
var ReactIs = require('react-is');

// By explicitly using `prop-types` you are opting into new development behavior.
// http://fb.me/prop-types-in-prod
var throwOnDirectAccess = true;
module.exports = require('./factoryWithTypeCheckers')(isValidElement, throwOnDirectAccess);
module.exports = require('./factoryWithTypeCheckers')(ReactIs.isElement, throwOnDirectAccess);
} else {
// By explicitly using `prop-types` you are opting into new production behavior.
// http://fb.me/prop-types-in-prod
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -25,7 +25,8 @@
},
"homepage": "https://facebook.github.io/react/",
"dependencies": {
"object-assign": "^4.1.1"
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
},
"scripts": {
"test": "jest",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -2356,6 +2356,11 @@ randombytes@^2.0.0, randombytes@^2.0.1:
dependencies:
safe-buffer "^5.1.0"

react-is@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb"
integrity sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA==

react@^15.5.1:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
Expand Down