@samitouri / QOS-React-2 / commits / 770b579aa2

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;