Update PropTypes for ReactElement & ReactNode
Paul O’Shannessy committed
Oct 13, 2014 at 22:35 UTC
770b579aa2f517f2a323234ccaa1445a0b7ca9cb
3 files changed
+67
-44
src/core/ReactPropTypes.js
+30
-14
@@ -14,6 +14,7 @@
14
var ReactElement = require('ReactElement');
15
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
16
17
+var deprecated = require('deprecated');
18
var emptyFunction = require('emptyFunction');
19
20
/**
@@ -65,6 +66,9 @@ var emptyFunction = require('emptyFunction');
66
67
var ANONYMOUS = '<<anonymous>>';
68
69
+var elementTypeChecker = createElementTypeChecker();
70
+var nodeTypeChecker = createNodeChecker();
71
+
72
var ReactPropTypes = {
73
array: createPrimitiveTypeChecker('array'),
74
bool: createPrimitiveTypeChecker('boolean'),
@@ -75,13 +79,28 @@ var ReactPropTypes = {
79
80
any: createAnyTypeChecker(),
81
arrayOf: createArrayOfTypeChecker,
78
- component: createComponentTypeChecker(),
82
+ element: elementTypeChecker,
83
instanceOf: createInstanceTypeChecker,
84
+ node: nodeTypeChecker,
85
objectOf: createObjectOfTypeChecker,
86
oneOf: createEnumTypeChecker,
87
oneOfType: createUnionTypeChecker,
83
- renderable: createRenderableTypeChecker(),
84
- shape: createShapeTypeChecker
88
+ shape: createShapeTypeChecker,
89
+
90
+ component: deprecated(
91
+ 'React.PropTypes',
92
+ 'component',
93
+ 'element',
94
+ this,
95
+ elementTypeChecker
96
+ ),
97
+ renderable: deprecated(
98
+ 'React.PropTypes',
99
+ 'renderable',
100
+ 'node',
101
+ this,
102
+ nodeTypeChecker
103
+ )
104
};
105
106
function createChainableTypeChecker(validate) {
@@ -151,13 +170,13 @@ function createArrayOfTypeChecker(typeChecker) {
170
return createChainableTypeChecker(validate);
171
}
172
154
-function createComponentTypeChecker() {
173
+function createElementTypeChecker() {
174
function validate(props, propName, componentName, location) {
175
if (!ReactElement.isValidElement(props[propName])) {
176
var locationName = ReactPropTypeLocationNames[location];
177
return new Error(
178
`Invalid ${locationName} \`${propName}\` supplied to ` +
160
- `\`${componentName}\`, expected a React component.`
179
+ `\`${componentName}\`, expected a ReactElement.`
180
);
181
}
182
}
@@ -238,13 +257,13 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
257
return createChainableTypeChecker(validate);
258
}
259
241
-function createRenderableTypeChecker() {
260
+function createNodeChecker() {
261
function validate(props, propName, componentName, location) {
243
- if (!isRenderable(props[propName])) {
262
+ if (!isNode(props[propName])) {
263
var locationName = ReactPropTypeLocationNames[location];
264
return new Error(
265
`Invalid ${locationName} \`${propName}\` supplied to ` +
247
- `\`${componentName}\`, expected a renderable prop.`
266
+ `\`${componentName}\`, expected a ReactNode.`
267
);
268
}
269
}
@@ -276,11 +295,8 @@ function createShapeTypeChecker(shapeTypes) {
295
return createChainableTypeChecker(validate, 'expected `object`');
296
}
297
279
-function isRenderable(propValue) {
298
+function isNode(propValue) {
299
switch(typeof propValue) {
281
- // TODO: this was probably written with the assumption that we're not
282
- // returning `this.props.component` directly from `render`. This is
283
- // currently not supported but we should, to make it consistent.
300
case 'number':
301
case 'string':
302
return true;
@@ -288,13 +304,13 @@ function isRenderable(propValue) {
304
return !propValue;
305
case 'object':
306
if (Array.isArray(propValue)) {
291
- return propValue.every(isRenderable);
307
+ return propValue.every(isNode);
308
}
309
if (ReactElement.isValidElement(propValue)) {
310
return true;
311
}
312
for (var k in propValue) {
297
- if (!isRenderable(propValue[k])) {
313
+ if (!isNode(propValue[k])) {
314
return false;
315
}
316
}
src/core/__tests__/ReactPropTypes-test.js
+34
-29
@@ -224,7 +224,7 @@ describe('ReactPropTypes', function() {
224
beforeEach(function() {
225
Component = React.createClass({
226
propTypes: {
227
- label: PropTypes.component.isRequired
227
+ label: PropTypes.element.isRequired
228
},
229
230
render: function() {
@@ -235,16 +235,16 @@ describe('ReactPropTypes', function() {
235
});
236
237
it('should support components', () => {
238
- typeCheckPass(PropTypes.component, <div />);
238
+ typeCheckPass(PropTypes.element, <div />);
239
});
240
241
it('should not support multiple components or scalar values', () => {
242
var message = 'Invalid prop `testProp` supplied to `testComponent`, ' +
243
- 'expected a React component.';
244
- typeCheckFail(PropTypes.component, [<div />, <div />], message);
245
- typeCheckFail(PropTypes.component, 123, message);
246
- typeCheckFail(PropTypes.component, 'foo', message);
247
- typeCheckFail(PropTypes.component, false, message);
243
+ 'expected a ReactElement.';
244
+ typeCheckFail(PropTypes.element, [<div />, <div />], message);
245
+ typeCheckFail(PropTypes.element, 123, message);
246
+ typeCheckFail(PropTypes.element, 'foo', message);
247
+ typeCheckFail(PropTypes.element, false, message);
248
});
249
250
it('should be able to define a single child as label', () => {
@@ -262,13 +262,13 @@ describe('ReactPropTypes', function() {
262
});
263
264
it("should be implicitly optional and not warn without values", function() {
265
- typeCheckPass(PropTypes.component, null);
266
- typeCheckPass(PropTypes.component, undefined);
265
+ typeCheckPass(PropTypes.element, null);
266
+ typeCheckPass(PropTypes.element, undefined);
267
});
268
269
it("should warn for missing required values", function() {
270
- typeCheckFail(PropTypes.component.isRequired, null, requiredMessage);
271
- typeCheckFail(PropTypes.component.isRequired, undefined, requiredMessage);
270
+ typeCheckFail(PropTypes.element.isRequired, null, requiredMessage);
271
+ typeCheckFail(PropTypes.element.isRequired, undefined, requiredMessage);
272
});
273
});
274
@@ -349,20 +349,21 @@ describe('ReactPropTypes', function() {
349
350
it('should warn for invalid values', function() {
351
var failMessage = 'Invalid prop `testProp` supplied to ' +
352
- '`testComponent`, expected a renderable prop.';
353
- typeCheckFail(PropTypes.renderable, true, failMessage);
354
- typeCheckFail(PropTypes.renderable, function() {}, failMessage);
355
- typeCheckFail(PropTypes.renderable, {key: function() {}}, failMessage);
352
+ '`testComponent`, expected a ReactNode.';
353
+ typeCheckFail(PropTypes.node, true, failMessage);
354
+ typeCheckFail(PropTypes.node, function() {}, failMessage);
355
+ typeCheckFail(PropTypes.node, {key: function() {}}, failMessage);
356
});
357
358
it('should not warn for valid values', function() {
359
- typeCheckPass(PropTypes.renderable, <div />);
360
- typeCheckPass(PropTypes.renderable, false);
361
- typeCheckPass(PropTypes.renderable, <MyComponent />);
362
- typeCheckPass(PropTypes.renderable, 'Some string');
363
- typeCheckPass(PropTypes.renderable, []);
364
- typeCheckPass(PropTypes.renderable, {});
365
- typeCheckPass(PropTypes.renderable, [
359
+ typeCheckPass(PropTypes.node, <div />);
360
+ typeCheckPass(PropTypes.node, false);
361
+ typeCheckPass(PropTypes.node, <MyComponent />);
362
+ typeCheckPass(PropTypes.node, 'Some string');
363
+ typeCheckPass(PropTypes.node, []);
364
+ typeCheckPass(PropTypes.node, {});
365
+
366
+ typeCheckPass(PropTypes.node, [
367
123,
368
'Some string',
369
<div />,
@@ -371,7 +372,7 @@ describe('ReactPropTypes', function() {
372
]);
373
374
// Object of rendereable things
374
- typeCheckPass(PropTypes.renderable, {
375
+ typeCheckPass(PropTypes.node, {
376
k0: 123,
377
k1: 'Some string',
378
k2: <div />,
@@ -384,26 +385,30 @@ describe('ReactPropTypes', function() {
385
});
386
387
it('should not warn for null/undefined if not required', function() {
387
- typeCheckPass(PropTypes.renderable, null);
388
- typeCheckPass(PropTypes.renderable, undefined);
388
+ typeCheckPass(PropTypes.node, null);
389
+ typeCheckPass(PropTypes.node, undefined);
390
});
391
392
it('should warn for missing required values', function() {
393
typeCheckFail(
393
- PropTypes.renderable.isRequired,
394
+ PropTypes.node.isRequired,
395
null,
396
'Required prop `testProp` was not specified in `testComponent`.'
397
);
398
typeCheckFail(
398
- PropTypes.renderable.isRequired,
399
+ PropTypes.node.isRequired,
400
undefined,
401
'Required prop `testProp` was not specified in `testComponent`.'
402
);
403
});
404
404
- it('should accept empty array & object for required props', function() {
405
+ it('should accept empty array for required props', function() {
406
+ typeCheckPass(PropTypes.node.isRequired, []);
407
+ });
408
+
409
+ it('should still work for deprecated typechecks', function() {
410
+ typeCheckPass(PropTypes.renderable, []);
411
typeCheckPass(PropTypes.renderable.isRequired, []);
406
- typeCheckPass(PropTypes.renderable.isRequired, {});
412
});
413
});
414
src/utils/deprecated.js
+3
-1
@@ -35,7 +35,9 @@ function deprecated(namespace, oldName, newName, ctx, fn) {
35
return fn.apply(ctx, arguments);
36
};
37
newFn.displayName = `${namespace}_${oldName}`;
38
- return newFn;
38
+ // We need to make sure all properties of the original fn are copied over.
39
+ // In particular, this is needed to support PropTypes
40
+ return Object.assign(newFn, fn);
41
}
42
43
return fn;