Validate propTypes, contextTypes, childContextTypes
who watches the watchmen?
Marshall Roch committed
Nov 27, 2013 at 12:00 UTC
351dcfed01d51dd18016d84ded4f86bddecaa7ba
4 files changed
+117
-13
src/core/ReactCompositeComponent.js
+33
-3
@@ -26,6 +26,7 @@ var ReactOwner = require('ReactOwner');
26
var ReactPerf = require('ReactPerf');
27
var ReactPropTransferer = require('ReactPropTransferer');
28
var ReactPropTypeLocations = require('ReactPropTypeLocations');
29
+var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
30
var ReactUpdates = require('ReactUpdates');
31
32
var invariant = require('invariant');
@@ -319,16 +320,46 @@ var RESERVED_SPEC_KEYS = {
320
}
321
},
322
childContextTypes: function(Constructor, childContextTypes) {
323
+ validateTypeDef(
324
+ Constructor,
325
+ childContextTypes,
326
+ ReactPropTypeLocations.childContext
327
+ );
328
Constructor.childContextTypes = childContextTypes;
329
},
330
contextTypes: function(Constructor, contextTypes) {
331
+ validateTypeDef(
332
+ Constructor,
333
+ contextTypes,
334
+ ReactPropTypeLocations.context
335
+ );
336
Constructor.contextTypes = contextTypes;
337
},
338
propTypes: function(Constructor, propTypes) {
339
+ validateTypeDef(
340
+ Constructor,
341
+ propTypes,
342
+ ReactPropTypeLocations.prop
343
+ );
344
Constructor.propTypes = propTypes;
345
}
346
};
347
348
+function validateTypeDef(Constructor, typeDef, location) {
349
+ for (var propName in typeDef) {
350
+ if (typeDef.hasOwnProperty(propName)) {
351
+ invariant(
352
+ typeof typeDef[propName] == 'function',
353
+ '%s: %s type `%s` is invalid; it must be a function, usually from ' +
354
+ 'React.PropTypes.',
355
+ Constructor.displayName || 'ReactCompositeComponent',
356
+ ReactPropTypeLocationNames[location],
357
+ propName
358
+ );
359
+ }
360
+ }
361
+}
362
+
363
function validateMethodOverride(proto, name) {
364
var specPolicy = ReactCompositeComponentInterface[name];
365
@@ -790,9 +821,8 @@ var ReactCompositeComponentMixin = {
821
_checkPropTypes: function(propTypes, props, location) {
822
var componentName = this.constructor.displayName;
823
for (var propName in propTypes) {
793
- var checkProp = propTypes[propName];
794
- if (checkProp) {
795
- checkProp(props, propName, componentName, location);
824
+ if (propTypes.hasOwnProperty(propName)) {
825
+ propTypes[propName](props, propName, componentName, location);
826
}
827
}
828
},
src/core/ReactPropTypeLocationNames.js
new
+31
@@ -0,0 +1,31 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactPropTypeLocationNames
17
+ */
18
+
19
+"use strict";
20
+
21
+var ReactPropTypeLocationNames = {};
22
+
23
+if (__DEV__) {
24
+ ReactPropTypeLocationNames = {
25
+ prop: 'prop',
26
+ context: 'context',
27
+ childContext: 'child context'
28
+ };
29
+}
30
+
31
+module.exports = ReactPropTypeLocationNames;
src/core/ReactPropTypes.js
+2
-10
@@ -18,19 +18,11 @@
18
19
"use strict";
20
21
+var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
22
+
23
var createObjectFrom = require('createObjectFrom');
24
var invariant = require('invariant');
25
24
-var ReactPropTypeLocationNames = {};
25
-
26
-if (__DEV__) {
27
- ReactPropTypeLocationNames = {
28
- prop: 'prop',
29
- context: 'context',
30
- childContext: 'child context'
31
- };
32
-}
33
-
26
/**
27
* Collection of methods that allow declaration and validation of props that are
28
* supplied to React components. Example usage:
src/core/__tests__/ReactCompositeComponent-test.js
+51
@@ -289,6 +289,57 @@ describe('ReactCompositeComponent', function() {
289
}).not.toThrow();
290
});
291
292
+ it('should throw on invalid prop types', function() {
293
+ expect(function() {
294
+ React.createClass({
295
+ displayName: 'Component',
296
+ propTypes: {
297
+ key: null
298
+ },
299
+ render: function() {
300
+ return <span>{this.props.key}</span>;
301
+ }
302
+ });
303
+ }).toThrow(
304
+ 'Invariant Violation: Component: prop type `key` is invalid; ' +
305
+ 'it must be a function, usually from React.PropTypes.'
306
+ );
307
+ });
308
+
309
+ it('should throw on invalid context types', function() {
310
+ expect(function() {
311
+ React.createClass({
312
+ displayName: 'Component',
313
+ contextTypes: {
314
+ key: null
315
+ },
316
+ render: function() {
317
+ return <span>{this.props.key}</span>;
318
+ }
319
+ });
320
+ }).toThrow(
321
+ 'Invariant Violation: Component: context type `key` is invalid; ' +
322
+ 'it must be a function, usually from React.PropTypes.'
323
+ );
324
+ });
325
+
326
+ it('should throw on invalid child context types', function() {
327
+ expect(function() {
328
+ React.createClass({
329
+ displayName: 'Component',
330
+ childContextTypes: {
331
+ key: null
332
+ },
333
+ render: function() {
334
+ return <span>{this.props.key}</span>;
335
+ }
336
+ });
337
+ }).toThrow(
338
+ 'Invariant Violation: Component: child context type `key` is invalid; ' +
339
+ 'it must be a function, usually from React.PropTypes.'
340
+ );
341
+ });
342
+
343
it('should not allow `forceUpdate` on unmounted components', function() {
344
var container = document.createElement('div');
345
document.documentElement.appendChild(container);