Introduce PropTypes.shape
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 }) } ```
Christopher Chedeau committed
Jan 15, 2014 at 16:26 UTC
9ade3c26d316a67d37406a662d5c3da43c9310d0
2 files changed
+104
-4
src/core/ReactPropTypes.js
+41
-4
@@ -81,6 +81,7 @@ var Props = {
81
object: createPrimitiveTypeChecker('object'),
82
string: createPrimitiveTypeChecker('string'),
83
84
+ shape: createShapeTypeChecker,
85
oneOf: createEnumTypeChecker,
86
oneOfType: createUnionTypeChecker,
87
@@ -128,14 +129,20 @@ function isRenderable(propValue) {
129
}
130
}
131
132
+// Equivalent of typeof but with special handling for arrays
133
+function getPropType(propValue) {
134
+ var propType = typeof propValue;
135
+ if (propType === 'object' && Array.isArray(propValue)) {
136
+ return 'array';
137
+ }
138
+ return propType;
139
+}
140
+
141
function createPrimitiveTypeChecker(expectedType) {
142
function validatePrimitiveType(
143
shouldThrow, propValue, propName, componentName, location
144
) {
135
- var propType = typeof propValue;
136
- if (propType === 'object' && Array.isArray(propValue)) {
137
- propType = 'array';
138
- }
145
+ var propType = getPropType(propValue);
146
var isValid = propType === expectedType;
147
if (!shouldThrow) {
148
return isValid;
@@ -174,6 +181,36 @@ function createEnumTypeChecker(expectedValues) {
181
return createChainableTypeChecker(validateEnumType);
182
}
183
184
+function createShapeTypeChecker(shapeTypes) {
185
+ function validateShapeType(
186
+ shouldThrow, propValue, propName, componentName, location
187
+ ) {
188
+ var propType = getPropType(propValue);
189
+ var isValid = propType === 'object';
190
+ if (isValid) {
191
+ for (var key in shapeTypes) {
192
+ var checker = shapeTypes[key];
193
+ if (checker) {
194
+ // It's going to throw an exception if it doesn't pass
195
+ checker(propValue, key, componentName, location);
196
+ }
197
+ }
198
+ }
199
+ if (!shouldThrow) {
200
+ return isValid;
201
+ }
202
+ invariant(
203
+ isValid,
204
+ 'Invalid %s `%s` of type `%s` supplied to `%s`, expected `object`.',
205
+ ReactPropTypeLocationNames[location],
206
+ propName,
207
+ propType,
208
+ componentName
209
+ );
210
+ }
211
+ return createChainableTypeChecker(validateShapeType);
212
+}
213
+
214
function createInstanceTypeChecker(expectedClass) {
215
function validateInstanceType(
216
shouldThrow, propValue, propName, componentName, location
src/core/__tests__/ReactPropTypes-test.js
+63
@@ -141,6 +141,69 @@ describe('Enum Types', function() {
141
});
142
});
143
144
+describe('Shape Types', function() {
145
+ beforeEach(function() {
146
+ require('mock-modules').dumpCache();
147
+ });
148
+
149
+ it("should throw for non objects", function() {
150
+ expect(typeCheck(Props.shape({}), 'some string')).toThrow(
151
+ 'Invariant Violation: Invalid prop `testProp` of type `string` ' +
152
+ 'supplied to `testComponent`, expected `object`.'
153
+ );
154
+ expect(typeCheck(Props.shape({}), ['array'])).toThrow(
155
+ 'Invariant Violation: Invalid prop `testProp` of type `array` ' +
156
+ 'supplied to `testComponent`, expected `object`.'
157
+ );
158
+ });
159
+
160
+ it("should not throw for empty values", function() {
161
+ expect(typeCheck(Props.shape({}), undefined)).not.toThrow();
162
+ expect(typeCheck(Props.shape({}), null)).not.toThrow();
163
+ expect(typeCheck(Props.shape({}), {})).not.toThrow();
164
+ });
165
+
166
+ it("should throw for empty required value", function() {
167
+ expect(typeCheck(Props.shape({}).isRequired, undefined)).toThrow(
168
+ 'Invariant Violation: Required prop `testProp` was not specified in ' +
169
+ '`testComponent`.'
170
+ );
171
+ expect(typeCheck(Props.shape({}).isRequired, null)).toThrow(
172
+ 'Invariant Violation: Required prop `testProp` was not specified in ' +
173
+ '`testComponent`.'
174
+ );
175
+ expect(typeCheck(Props.shape({}).isRequired, {})).not.toThrow();
176
+ });
177
+
178
+ it("should not throw for non specified types", function() {
179
+ expect(typeCheck(Props.shape({}), {key: 1})).not.toThrow();
180
+ });
181
+
182
+ it("should not throw for valid types", function() {
183
+ expect(typeCheck(Props.shape({
184
+ key: Props.number
185
+ }), {key: 1})).not.toThrow();
186
+ });
187
+
188
+ it("should throw for required valid types", function() {
189
+ expect(typeCheck(Props.shape({
190
+ key: Props.number.isRequired
191
+ }), {})).toThrow(
192
+ 'Invariant Violation: Required prop `key` was not specified in ' +
193
+ '`testComponent`.'
194
+ );
195
+ });
196
+
197
+ it("should throw for invalid key types", function() {
198
+ expect(typeCheck(Props.shape({
199
+ key: Props.number
200
+ }), {key: 'abc'})).toThrow(
201
+ 'Invariant Violation: Invalid prop `key` of type `string` supplied to ' +
202
+ '`testComponent`, expected `number`.'
203
+ );
204
+ });
205
+});
206
+
207
describe('Instance Types', function() {
208
beforeEach(function() {
209
require('mock-modules').dumpCache();