UNPKG

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