UNPKG

7.95 kBJavaScriptView Raw
1/**
2 * Copyright 2014-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 */
10
11/**
12 * ReactElementValidator provides a wrapper around a element factory
13 * which validates the props passed to the element. This is intended to be
14 * used only in DEV and could be replaced by a static type checker for languages
15 * that support it.
16 */
17
18'use strict';
19
20var ReactCurrentOwner = require('./ReactCurrentOwner');
21var ReactComponentTreeHook = require('./ReactComponentTreeHook');
22var ReactElement = require('./ReactElement');
23
24var checkReactTypeSpec = require('./checkReactTypeSpec');
25
26var canDefineProperty = require('./canDefineProperty');
27var getIteratorFn = require('./getIteratorFn');
28var warning = require('fbjs/lib/warning');
29
30function getDeclarationErrorAddendum() {
31 if (ReactCurrentOwner.current) {
32 var name = ReactCurrentOwner.current.getName();
33 if (name) {
34 return ' Check the render method of `' + name + '`.';
35 }
36 }
37 return '';
38}
39
40/**
41 * Warn if there's no key explicitly set on dynamic arrays of children or
42 * object keys are not valid. This allows us to keep track of children between
43 * updates.
44 */
45var ownerHasKeyUseWarning = {};
46
47function getCurrentComponentErrorInfo(parentType) {
48 var info = getDeclarationErrorAddendum();
49
50 if (!info) {
51 var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name;
52 if (parentName) {
53 info = ' Check the top-level render call using <' + parentName + '>.';
54 }
55 }
56 return info;
57}
58
59/**
60 * Warn if the element doesn't have an explicit key assigned to it.
61 * This element is in an array. The array could grow and shrink or be
62 * reordered. All children that haven't already been validated are required to
63 * have a "key" property assigned to it. Error statuses are cached so a warning
64 * will only be shown once.
65 *
66 * @internal
67 * @param {ReactElement} element Element that requires a key.
68 * @param {*} parentType element's parent's type.
69 */
70function validateExplicitKey(element, parentType) {
71 if (!element._store || element._store.validated || element.key != null) {
72 return;
73 }
74 element._store.validated = true;
75
76 var memoizer = ownerHasKeyUseWarning.uniqueKey || (ownerHasKeyUseWarning.uniqueKey = {});
77
78 var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
79 if (memoizer[currentComponentErrorInfo]) {
80 return;
81 }
82 memoizer[currentComponentErrorInfo] = true;
83
84 // Usually the current owner is the offender, but if it accepts children as a
85 // property, it may be the creator of the child that's responsible for
86 // assigning it a key.
87 var childOwner = '';
88 if (element && element._owner && element._owner !== ReactCurrentOwner.current) {
89 // Give the component that originally created this child.
90 childOwner = ' It was passed a child from ' + element._owner.getName() + '.';
91 }
92
93 process.env.NODE_ENV !== 'production' ? warning(false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, ReactComponentTreeHook.getCurrentStackAddendum(element)) : void 0;
94}
95
96/**
97 * Ensure that every element either is passed in a static location, in an
98 * array with an explicit keys property defined, or in an object literal
99 * with valid key property.
100 *
101 * @internal
102 * @param {ReactNode} node Statically passed child of any type.
103 * @param {*} parentType node's parent's type.
104 */
105function validateChildKeys(node, parentType) {
106 if (typeof node !== 'object') {
107 return;
108 }
109 if (Array.isArray(node)) {
110 for (var i = 0; i < node.length; i++) {
111 var child = node[i];
112 if (ReactElement.isValidElement(child)) {
113 validateExplicitKey(child, parentType);
114 }
115 }
116 } else if (ReactElement.isValidElement(node)) {
117 // This element was passed in a valid location.
118 if (node._store) {
119 node._store.validated = true;
120 }
121 } else if (node) {
122 var iteratorFn = getIteratorFn(node);
123 // Entry iterators provide implicit keys.
124 if (iteratorFn) {
125 if (iteratorFn !== node.entries) {
126 var iterator = iteratorFn.call(node);
127 var step;
128 while (!(step = iterator.next()).done) {
129 if (ReactElement.isValidElement(step.value)) {
130 validateExplicitKey(step.value, parentType);
131 }
132 }
133 }
134 }
135 }
136}
137
138/**
139 * Given an element, validate that its props follow the propTypes definition,
140 * provided by the type.
141 *
142 * @param {ReactElement} element
143 */
144function validatePropTypes(element) {
145 var componentClass = element.type;
146 if (typeof componentClass !== 'function') {
147 return;
148 }
149 var name = componentClass.displayName || componentClass.name;
150 if (componentClass.propTypes) {
151 checkReactTypeSpec(componentClass.propTypes, element.props, 'prop', name, element, null);
152 }
153 if (typeof componentClass.getDefaultProps === 'function') {
154 process.env.NODE_ENV !== 'production' ? warning(componentClass.getDefaultProps.isReactClassApproved, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.') : void 0;
155 }
156}
157
158var ReactElementValidator = {
159
160 createElement: function (type, props, children) {
161 var validType = typeof type === 'string' || typeof type === 'function';
162 // We warn in this case but don't throw. We expect the element creation to
163 // succeed and there will likely be errors in render.
164 if (!validType) {
165 process.env.NODE_ENV !== 'production' ? warning(false, 'React.createElement: type should not be null, undefined, boolean, or ' + 'number. It should be a string (for DOM elements) or a ReactClass ' + '(for composite components).%s', getDeclarationErrorAddendum()) : void 0;
166 }
167
168 var element = ReactElement.createElement.apply(this, arguments);
169
170 // The result can be nullish if a mock or a custom function is used.
171 // TODO: Drop this when these are no longer allowed as the type argument.
172 if (element == null) {
173 return element;
174 }
175
176 // Skip key warning if the type isn't valid since our key validation logic
177 // doesn't expect a non-string/function type and can throw confusing errors.
178 // We don't want exception behavior to differ between dev and prod.
179 // (Rendering will throw with a helpful message and as soon as the type is
180 // fixed, the key warnings will appear.)
181 if (validType) {
182 for (var i = 2; i < arguments.length; i++) {
183 validateChildKeys(arguments[i], type);
184 }
185 }
186
187 validatePropTypes(element);
188
189 return element;
190 },
191
192 createFactory: function (type) {
193 var validatedFactory = ReactElementValidator.createElement.bind(null, type);
194 // Legacy hook TODO: Warn if this is accessed
195 validatedFactory.type = type;
196
197 if (process.env.NODE_ENV !== 'production') {
198 if (canDefineProperty) {
199 Object.defineProperty(validatedFactory, 'type', {
200 enumerable: false,
201 get: function () {
202 process.env.NODE_ENV !== 'production' ? warning(false, 'Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.') : void 0;
203 Object.defineProperty(this, 'type', {
204 value: type
205 });
206 return type;
207 }
208 });
209 }
210 }
211
212 return validatedFactory;
213 },
214
215 cloneElement: function (element, props, children) {
216 var newElement = ReactElement.cloneElement.apply(this, arguments);
217 for (var i = 2; i < arguments.length; i++) {
218 validateChildKeys(arguments[i], newElement.type);
219 }
220 validatePropTypes(newElement);
221 return newElement;
222 }
223
224};
225
226module.exports = ReactElementValidator;
\No newline at end of file