mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ReactPropTypes: add oneOfType to support union types
Summary:
e.g.
propTypes: {
foo: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]);
}
to do this, I exposed a `.weak` chainable validator that returns instead of throws, e.g.
React.PropTypes.string.isRequired.weak
React.PropTypes.string.weak
which returns true or false. Technically, `React.PropTypes.string.weak.isRequired` also exists, but `oneOfType` will never call it. Might be useful to people creating custom validators though.
This commit is contained in:
committed by
Paul O’Shannessy
parent
351dcfed01
commit
697a9113c0
+81
-26
@@ -81,6 +81,7 @@ var Props = {
|
||||
string: createPrimitiveTypeChecker('string'),
|
||||
|
||||
oneOf: createEnumTypeChecker,
|
||||
oneOfType: createUnionTypeChecker,
|
||||
|
||||
instanceOf: createInstanceTypeChecker
|
||||
|
||||
@@ -89,13 +90,19 @@ var Props = {
|
||||
var ANONYMOUS = '<<anonymous>>';
|
||||
|
||||
function createPrimitiveTypeChecker(expectedType) {
|
||||
function validatePrimitiveType(propValue, propName, componentName, location) {
|
||||
function validatePrimitiveType(
|
||||
shouldThrow, propValue, propName, componentName, location
|
||||
) {
|
||||
var propType = typeof propValue;
|
||||
if (propType === 'object' && Array.isArray(propValue)) {
|
||||
propType = 'array';
|
||||
}
|
||||
var isValid = propType === expectedType;
|
||||
if (!shouldThrow) {
|
||||
return isValid;
|
||||
}
|
||||
invariant(
|
||||
propType === expectedType,
|
||||
isValid,
|
||||
'Invalid %s `%s` of type `%s` supplied to `%s`, expected `%s`.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
@@ -109,9 +116,15 @@ function createPrimitiveTypeChecker(expectedType) {
|
||||
|
||||
function createEnumTypeChecker(expectedValues) {
|
||||
var expectedEnum = createObjectFrom(expectedValues);
|
||||
function validateEnumType(propValue, propName, componentName, location) {
|
||||
function validateEnumType(
|
||||
shouldThrow, propValue, propName, componentName, location
|
||||
) {
|
||||
var isValid = expectedEnum[propValue];
|
||||
if (!shouldThrow) {
|
||||
return isValid;
|
||||
}
|
||||
invariant(
|
||||
expectedEnum[propValue],
|
||||
isValid,
|
||||
'Invalid %s `%s` supplied to `%s`, expected one of %s.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
@@ -123,9 +136,15 @@ function createEnumTypeChecker(expectedValues) {
|
||||
}
|
||||
|
||||
function createInstanceTypeChecker(expectedClass) {
|
||||
function validateInstanceType(propValue, propName, componentName, location) {
|
||||
function validateInstanceType(
|
||||
shouldThrow, propValue, propName, componentName, location
|
||||
) {
|
||||
var isValid = propValue instanceof expectedClass;
|
||||
if (!shouldThrow) {
|
||||
return isValid;
|
||||
}
|
||||
invariant(
|
||||
propValue instanceof expectedClass,
|
||||
isValid,
|
||||
'Invalid %s `%s` supplied to `%s`, expected instance of `%s`.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
@@ -137,28 +156,64 @@ function createInstanceTypeChecker(expectedClass) {
|
||||
}
|
||||
|
||||
function createChainableTypeChecker(validate) {
|
||||
function createTypeChecker(isRequired) {
|
||||
function checkType(props, propName, componentName, location) {
|
||||
var propValue = props[propName];
|
||||
if (propValue != null) {
|
||||
// Only validate if there is a value to check.
|
||||
validate(propValue, propName, componentName || ANONYMOUS, location);
|
||||
} else {
|
||||
invariant(
|
||||
!isRequired,
|
||||
'Required %s `%s` was not specified in `%s`.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
componentName || ANONYMOUS
|
||||
);
|
||||
function checkType(
|
||||
isRequired, shouldThrow, props, propName, componentName, location
|
||||
) {
|
||||
var propValue = props[propName];
|
||||
if (propValue != null) {
|
||||
// Only validate if there is a value to check.
|
||||
return validate(
|
||||
shouldThrow,
|
||||
propValue,
|
||||
propName,
|
||||
componentName || ANONYMOUS,
|
||||
location
|
||||
);
|
||||
} else {
|
||||
var isValid = !isRequired;
|
||||
if (!shouldThrow) {
|
||||
return isValid;
|
||||
}
|
||||
invariant(
|
||||
isValid,
|
||||
'Required %s `%s` was not specified in `%s`.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
componentName || ANONYMOUS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var checker = checkType.bind(null, false, true);
|
||||
checker.weak = checkType.bind(null, false, false);
|
||||
checker.isRequired = checkType.bind(null, true, true);
|
||||
checker.weak.isRequired = checkType.bind(null, true, false);
|
||||
checker.isRequired.weak = checker.weak.isRequired;
|
||||
|
||||
return checker;
|
||||
}
|
||||
|
||||
function createUnionTypeChecker(arrayOfValidators) {
|
||||
return function(props, propName, componentName, location) {
|
||||
var isValid = false;
|
||||
for (var ii = 0; ii < arrayOfValidators.length; ii++) {
|
||||
var validate = arrayOfValidators[ii];
|
||||
if (typeof validate.weak === 'function') {
|
||||
validate = validate.weak;
|
||||
}
|
||||
if (validate(props, propName, componentName, location)) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isRequired) {
|
||||
checkType.isRequired = createTypeChecker(true);
|
||||
}
|
||||
return checkType;
|
||||
}
|
||||
return createTypeChecker(false);
|
||||
invariant(
|
||||
isValid,
|
||||
'Invalid %s `%s` supplied to `%s`.',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName,
|
||||
componentName || ANONYMOUS
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Props;
|
||||
|
||||
@@ -84,6 +84,12 @@ describe('Primitive Types', function() {
|
||||
'`testComponent`.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should have a weak version that returns true/false", function() {
|
||||
expect(typeCheck(Props.string.weak, null)()).toEqual(true);
|
||||
expect(typeCheck(Props.string.weak.isRequired, null)()).toEqual(false);
|
||||
expect(typeCheck(Props.string.isRequired.weak, null)()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enum Types', function() {
|
||||
@@ -115,6 +121,13 @@ describe('Enum Types', function() {
|
||||
expect(typeCheck(Props.oneOf(['red', 'blue']), null)).not.toThrow();
|
||||
expect(typeCheck(Props.oneOf(['red', 'blue']), undefined)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should have a weak version that returns true/false", function() {
|
||||
var checker = Props.oneOf(['red', 'blue']);
|
||||
expect(typeCheck(checker.weak, null)()).toEqual(true);
|
||||
expect(typeCheck(checker.weak.isRequired, null)()).toEqual(false);
|
||||
expect(typeCheck(checker.isRequired.weak, null)()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Instance Types', function() {
|
||||
@@ -148,3 +161,48 @@ describe('Instance Types', function() {
|
||||
expect(typeCheck(Props.instanceOf(Person), new Engineer())).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Union Types', function() {
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
});
|
||||
|
||||
it('should throw if none of the types are valid', function() {
|
||||
var checker = Props.oneOfType([
|
||||
Props.string,
|
||||
Props.number
|
||||
]);
|
||||
expect(typeCheck(checker, [])).toThrow(
|
||||
'Invariant Violation: Invalid prop `testProp` ' +
|
||||
'supplied to `testComponent`.'
|
||||
);
|
||||
|
||||
checker = Props.oneOfType([
|
||||
Props.string.isRequired,
|
||||
Props.number.isRequired
|
||||
]);
|
||||
expect(typeCheck(checker, null)).toThrow(
|
||||
'Invariant Violation: Invalid prop `testProp` ' +
|
||||
'supplied to `testComponent`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw if one of the types are valid', function() {
|
||||
var checker = Props.oneOfType([
|
||||
Props.string,
|
||||
Props.number
|
||||
]);
|
||||
expect(typeCheck(checker, null)).not.toThrow();
|
||||
expect(typeCheck(checker, 'foo')).not.toThrow();
|
||||
expect(typeCheck(checker, 123)).not.toThrow();
|
||||
|
||||
checker = Props.oneOfType([
|
||||
Props.string,
|
||||
Props.number.isRequired
|
||||
]);
|
||||
expect(typeCheck(checker, null)).not.toThrow();
|
||||
expect(typeCheck(checker, 'foo')).not.toThrow();
|
||||
expect(typeCheck(checker, 123)).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user