Skip to content

Commit

Permalink
[New] Add .elementType
Browse files Browse the repository at this point in the history
  • Loading branch information
eXon authored and ljharb committed Sep 9, 2018
1 parent 8343e81 commit b67bbd4
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 13 deletions.
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

0 comments on commit b67bbd4

Please sign in to comment.