Skip to content

Commit

Permalink
Introduce PropTypes.shape
Browse files Browse the repository at this point in the history
This diff introduces PropType.shape which lets you type an object. PropType.object was already defined and since it isn't a function I couldn't really overload the meaning to also accept a type list. Instead of doing hackery, I decided to name it `shape`.

An example where this could be used is style:

```
  propTypes: {
    style: PropTypes.shape({
      color: PropStyle.string,
      fontSize: PropTypes.number
    })
  }
```
  • Loading branch information
vjeux authored and zpao committed Jan 16, 2014
1 parent 0906d28 commit 9ade3c2
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 4 deletions.
45 changes: 41 additions & 4 deletions src/core/ReactPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var Props = {
object: createPrimitiveTypeChecker('object'),
string: createPrimitiveTypeChecker('string'),

shape: createShapeTypeChecker,
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,

Expand Down Expand Up @@ -128,14 +129,20 @@ function isRenderable(propValue) {
}
}

// Equivalent of typeof but with special handling for arrays
function getPropType(propValue) {
var propType = typeof propValue;
if (propType === 'object' && Array.isArray(propValue)) {
return 'array';
}
return propType;
}

function createPrimitiveTypeChecker(expectedType) {
function validatePrimitiveType(
shouldThrow, propValue, propName, componentName, location
) {
var propType = typeof propValue;
if (propType === 'object' && Array.isArray(propValue)) {
propType = 'array';
}
var propType = getPropType(propValue);
var isValid = propType === expectedType;
if (!shouldThrow) {
return isValid;
Expand Down Expand Up @@ -174,6 +181,36 @@ function createEnumTypeChecker(expectedValues) {
return createChainableTypeChecker(validateEnumType);
}

function createShapeTypeChecker(shapeTypes) {
function validateShapeType(
shouldThrow, propValue, propName, componentName, location
) {
var propType = getPropType(propValue);
var isValid = propType === 'object';
if (isValid) {
for (var key in shapeTypes) {
var checker = shapeTypes[key];
if (checker) {
// It's going to throw an exception if it doesn't pass
checker(propValue, key, componentName, location);
}
}
}
if (!shouldThrow) {
return isValid;
}
invariant(
isValid,
'Invalid %s `%s` of type `%s` supplied to `%s`, expected `object`.',
ReactPropTypeLocationNames[location],
propName,
propType,
componentName
);
}
return createChainableTypeChecker(validateShapeType);
}

function createInstanceTypeChecker(expectedClass) {
function validateInstanceType(
shouldThrow, propValue, propName, componentName, location
Expand Down
63 changes: 63 additions & 0 deletions src/core/__tests__/ReactPropTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,69 @@ describe('Enum Types', function() {
});
});

describe('Shape Types', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
});

it("should throw for non objects", function() {
expect(typeCheck(Props.shape({}), 'some string')).toThrow(
'Invariant Violation: Invalid prop `testProp` of type `string` ' +
'supplied to `testComponent`, expected `object`.'
);
expect(typeCheck(Props.shape({}), ['array'])).toThrow(
'Invariant Violation: Invalid prop `testProp` of type `array` ' +
'supplied to `testComponent`, expected `object`.'
);
});

it("should not throw for empty values", function() {
expect(typeCheck(Props.shape({}), undefined)).not.toThrow();
expect(typeCheck(Props.shape({}), null)).not.toThrow();
expect(typeCheck(Props.shape({}), {})).not.toThrow();
});

it("should throw for empty required value", function() {
expect(typeCheck(Props.shape({}).isRequired, undefined)).toThrow(
'Invariant Violation: Required prop `testProp` was not specified in ' +
'`testComponent`.'
);
expect(typeCheck(Props.shape({}).isRequired, null)).toThrow(
'Invariant Violation: Required prop `testProp` was not specified in ' +
'`testComponent`.'
);
expect(typeCheck(Props.shape({}).isRequired, {})).not.toThrow();
});

it("should not throw for non specified types", function() {
expect(typeCheck(Props.shape({}), {key: 1})).not.toThrow();
});

it("should not throw for valid types", function() {
expect(typeCheck(Props.shape({
key: Props.number
}), {key: 1})).not.toThrow();
});

it("should throw for required valid types", function() {
expect(typeCheck(Props.shape({
key: Props.number.isRequired
}), {})).toThrow(
'Invariant Violation: Required prop `key` was not specified in ' +
'`testComponent`.'
);
});

it("should throw for invalid key types", function() {
expect(typeCheck(Props.shape({
key: Props.number
}), {key: 'abc'})).toThrow(
'Invariant Violation: Invalid prop `key` of type `string` supplied to ' +
'`testComponent`, expected `number`.'
);
});
});

describe('Instance Types', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
Expand Down

0 comments on commit 9ade3c2

Please sign in to comment.