mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
This triggers a warning if you try to pass a keyed object as a child. You now have to wrap it in React.addons.createFragment(object) which creates a proxy (in dev) which warns if it is accessed. The purpose of this is to make these into opaque objects so that nobody relies on its data structure. After that we can turn it into a different data structure such as a ReactFragment node or an iterable of flattened ReactElements.
407 lines
12 KiB
JavaScript
407 lines
12 KiB
JavaScript
/**
|
|
* Copyright 2014-2015, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule ReactElementValidator
|
|
*/
|
|
|
|
/**
|
|
* ReactElementValidator provides a wrapper around a element factory
|
|
* which validates the props passed to the element. This is intended to be
|
|
* used only in DEV and could be replaced by a static type checker for languages
|
|
* that support it.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var ReactElement = require('ReactElement');
|
|
var ReactFragment = require('ReactFragment');
|
|
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
|
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
|
var ReactCurrentOwner = require('ReactCurrentOwner');
|
|
var ReactNativeComponent = require('ReactNativeComponent');
|
|
|
|
var getIteratorFn = require('getIteratorFn');
|
|
var monitorCodeUse = require('monitorCodeUse');
|
|
var invariant = require('invariant');
|
|
var warning = require('warning');
|
|
|
|
function getDeclarationErrorAddendum() {
|
|
if (ReactCurrentOwner.current) {
|
|
var name = ReactCurrentOwner.current.getName();
|
|
if (name) {
|
|
return ' Check the render method of `' + name + '`.';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Warn if there's no key explicitly set on dynamic arrays of children or
|
|
* object keys are not valid. This allows us to keep track of children between
|
|
* updates.
|
|
*/
|
|
var ownerHasKeyUseWarning = {
|
|
'react_key_warning': {},
|
|
'react_numeric_key_warning': {}
|
|
};
|
|
var ownerHasMonitoredObjectMap = {};
|
|
|
|
var loggedTypeFailures = {};
|
|
|
|
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
|
|
|
|
/**
|
|
* Gets the instance's name for use in warnings.
|
|
*
|
|
* @internal
|
|
* @return {?string} Display name or undefined
|
|
*/
|
|
function getName(instance) {
|
|
var publicInstance = instance && instance.getPublicInstance();
|
|
if (!publicInstance) {
|
|
return undefined;
|
|
}
|
|
var constructor = publicInstance.constructor;
|
|
if (!constructor) {
|
|
return undefined;
|
|
}
|
|
return constructor.displayName || constructor.name || undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets the current owner's displayName for use in warnings.
|
|
*
|
|
* @internal
|
|
* @return {?string} Display name or undefined
|
|
*/
|
|
function getCurrentOwnerDisplayName() {
|
|
var current = ReactCurrentOwner.current;
|
|
return (
|
|
current && getName(current) || undefined
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Warn if the element doesn't have an explicit key assigned to it.
|
|
* This element is in an array. The array could grow and shrink or be
|
|
* reordered. All children that haven't already been validated are required to
|
|
* have a "key" property assigned to it.
|
|
*
|
|
* @internal
|
|
* @param {ReactElement} element Element that requires a key.
|
|
* @param {*} parentType element's parent's type.
|
|
*/
|
|
function validateExplicitKey(element, parentType) {
|
|
if (element._store.validated || element.key != null) {
|
|
return;
|
|
}
|
|
element._store.validated = true;
|
|
|
|
warnAndMonitorForKeyUse(
|
|
'react_key_warning',
|
|
'Each child in an array or iterator should have a unique "key" prop.',
|
|
element,
|
|
parentType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Warn if the key is being defined as an object property but has an incorrect
|
|
* value.
|
|
*
|
|
* @internal
|
|
* @param {string} name Property name of the key.
|
|
* @param {ReactElement} element Component that requires a key.
|
|
* @param {*} parentType element's parent's type.
|
|
*/
|
|
function validatePropertyKey(name, element, parentType) {
|
|
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
|
|
return;
|
|
}
|
|
warnAndMonitorForKeyUse(
|
|
'react_numeric_key_warning',
|
|
'Child objects should have non-numeric keys so ordering is preserved.',
|
|
element,
|
|
parentType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Shared warning and monitoring code for the key warnings.
|
|
*
|
|
* @internal
|
|
* @param {string} warningID The id used when logging.
|
|
* @param {string} message The base warning that gets output.
|
|
* @param {ReactElement} element Component that requires a key.
|
|
* @param {*} parentType element's parent's type.
|
|
*/
|
|
function warnAndMonitorForKeyUse(warningID, message, element, parentType) {
|
|
var ownerName = getCurrentOwnerDisplayName();
|
|
var parentName = parentType.displayName || parentType.name;
|
|
|
|
var useName = ownerName || parentName;
|
|
var memoizer = ownerHasKeyUseWarning[warningID];
|
|
if (memoizer.hasOwnProperty(useName)) {
|
|
return;
|
|
}
|
|
memoizer[useName] = true;
|
|
|
|
message += ownerName ?
|
|
` Check the render method of ${ownerName}.` :
|
|
` Check the React.render call using <${parentName}>.`;
|
|
|
|
// Usually the current owner is the offender, but if it accepts children as a
|
|
// property, it may be the creator of the child that's responsible for
|
|
// assigning it a key.
|
|
var childOwnerName = null;
|
|
if (element &&
|
|
element._owner &&
|
|
element._owner !== ReactCurrentOwner.current) {
|
|
// Name of the component that originally created this child.
|
|
childOwnerName = getName(element._owner);
|
|
|
|
message += ` It was passed a child from ${childOwnerName}.`;
|
|
}
|
|
|
|
message += ' See http://fb.me/react-warning-keys for more information.';
|
|
monitorCodeUse(warningID, {
|
|
component: useName,
|
|
componentOwner: childOwnerName
|
|
});
|
|
console.warn(message);
|
|
}
|
|
|
|
/**
|
|
* Ensure that every element either is passed in a static location, in an
|
|
* array with an explicit keys property defined, or in an object literal
|
|
* with valid key property.
|
|
*
|
|
* @internal
|
|
* @param {ReactNode} node Statically passed child of any type.
|
|
* @param {*} parentType node's parent's type.
|
|
*/
|
|
function validateChildKeys(node, parentType) {
|
|
if (Array.isArray(node)) {
|
|
for (var i = 0; i < node.length; i++) {
|
|
var child = node[i];
|
|
if (ReactElement.isValidElement(child)) {
|
|
validateExplicitKey(child, parentType);
|
|
}
|
|
}
|
|
} else if (ReactElement.isValidElement(node)) {
|
|
// This element was passed in a valid location.
|
|
node._store.validated = true;
|
|
} else if (node) {
|
|
var iteratorFn = getIteratorFn(node);
|
|
// Entry iterators provide implicit keys.
|
|
if (iteratorFn) {
|
|
if (iteratorFn !== node.entries) {
|
|
var iterator = iteratorFn.call(node);
|
|
var step;
|
|
while (!(step = iterator.next()).done) {
|
|
if (ReactElement.isValidElement(step.value)) {
|
|
validateExplicitKey(step.value, parentType);
|
|
}
|
|
}
|
|
}
|
|
} else if (typeof node === 'object') {
|
|
var fragment = ReactFragment.extractIfFragment(node);
|
|
for (var key in fragment) {
|
|
if (fragment.hasOwnProperty(key)) {
|
|
validatePropertyKey(key, fragment[key], parentType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that the props are valid
|
|
*
|
|
* @param {string} componentName Name of the component for error messages.
|
|
* @param {object} propTypes Map of prop name to a ReactPropType
|
|
* @param {object} props
|
|
* @param {string} location e.g. "prop", "context", "child context"
|
|
* @private
|
|
*/
|
|
function checkPropTypes(componentName, propTypes, props, location) {
|
|
for (var propName in propTypes) {
|
|
if (propTypes.hasOwnProperty(propName)) {
|
|
var error;
|
|
// Prop type validation may throw. In case they do, we don't want to
|
|
// fail the render phase where it didn't fail before. So we log it.
|
|
// After these have been cleaned up, we'll let them throw.
|
|
try {
|
|
// This is intentionally an invariant that gets caught. It's the same
|
|
// behavior as without this statement except with a better message.
|
|
invariant(
|
|
typeof propTypes[propName] === 'function',
|
|
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
|
|
'React.PropTypes.',
|
|
componentName || 'React class',
|
|
ReactPropTypeLocationNames[location],
|
|
propName
|
|
);
|
|
error = propTypes[propName](props, propName, componentName, location);
|
|
} catch (ex) {
|
|
error = ex;
|
|
}
|
|
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
|
|
// Only monitor this failure once because there tends to be a lot of the
|
|
// same error.
|
|
loggedTypeFailures[error.message] = true;
|
|
|
|
var addendum = getDeclarationErrorAddendum(this);
|
|
warning(false, 'Failed propType: ' + error.message + addendum);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var warnedPropsMutations = {};
|
|
|
|
/**
|
|
* Warn about mutating props when setting `propName` on `element`.
|
|
*
|
|
* @param {string} propName The string key within props that was set
|
|
* @param {ReactElement} element
|
|
*/
|
|
function warnForPropsMutation(propName, element) {
|
|
var type = element.type;
|
|
var elementName = typeof type === 'string' ? type : type.displayName;
|
|
var ownerName = element._owner ?
|
|
element._owner.getPublicInstance().constructor.displayName : null;
|
|
|
|
var warningKey = propName + '|' + elementName + '|' + ownerName;
|
|
if (warnedPropsMutations.hasOwnProperty(warningKey)) {
|
|
return;
|
|
}
|
|
warnedPropsMutations[warningKey] = true;
|
|
|
|
var elementInfo = '';
|
|
if (elementName) {
|
|
elementInfo = ' <' + elementName + ' />';
|
|
}
|
|
var ownerInfo = '';
|
|
if (ownerName) {
|
|
ownerInfo = ' The element was created by ' + ownerName + '.';
|
|
}
|
|
|
|
warning(
|
|
false,
|
|
'Don\'t set .props.%s of the React component%s. ' +
|
|
'Instead, specify the correct value when ' +
|
|
'initially creating the element.%s',
|
|
propName,
|
|
elementInfo,
|
|
ownerInfo
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given an element, check if its props have been mutated since element
|
|
* creation (or the last call to this function). In particular, check if any
|
|
* new props have been added, which we can't directly catch by defining warning
|
|
* properties on the props object.
|
|
*
|
|
* @param {ReactElement} element
|
|
*/
|
|
function checkAndWarnForMutatedProps(element) {
|
|
if (!element._store) {
|
|
// Element was created using `new ReactElement` directly or with
|
|
// `ReactElement.createElement`; skip mutation checking
|
|
return;
|
|
}
|
|
|
|
var originalProps = element._store.originalProps;
|
|
var props = element.props;
|
|
|
|
for (var propName in props) {
|
|
if (props.hasOwnProperty(propName)) {
|
|
if (!originalProps.hasOwnProperty(propName) ||
|
|
originalProps[propName] !== props[propName]) {
|
|
warnForPropsMutation(propName, element);
|
|
|
|
// Copy over the new value so that the two props objects match again
|
|
originalProps[propName] = props[propName];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var ReactElementValidator = {
|
|
|
|
checkAndWarnForMutatedProps: checkAndWarnForMutatedProps,
|
|
|
|
createElement: function(type, props, children) {
|
|
// We warn in this case but don't throw. We expect the element creation to
|
|
// succeed and there will likely be errors in render.
|
|
warning(
|
|
type != null,
|
|
'React.createElement: type should not be null or undefined. It should ' +
|
|
'be a string (for DOM elements) or a ReactClass (for composite ' +
|
|
'components).'
|
|
);
|
|
|
|
var element = ReactElement.createElement.apply(this, arguments);
|
|
|
|
// The result can be nullish if a mock or a custom function is used.
|
|
// TODO: Drop this when these are no longer allowed as the type argument.
|
|
if (element == null) {
|
|
return element;
|
|
}
|
|
|
|
for (var i = 2; i < arguments.length; i++) {
|
|
validateChildKeys(arguments[i], type);
|
|
}
|
|
|
|
if (type) {
|
|
// Extract the component class from the element. Converts string types
|
|
// to a composite class which may have propTypes.
|
|
// TODO: Validating a string's propTypes is not decoupled from the
|
|
// rendering target which is problematic.
|
|
var componentClass = ReactNativeComponent.getComponentClassForElement(
|
|
element
|
|
);
|
|
var name = componentClass.displayName || componentClass.name;
|
|
if (__DEV__) {
|
|
if (componentClass.propTypes) {
|
|
checkPropTypes(
|
|
name,
|
|
componentClass.propTypes,
|
|
element.props,
|
|
ReactPropTypeLocations.prop
|
|
);
|
|
}
|
|
}
|
|
if (typeof componentClass.getDefaultProps === 'function') {
|
|
warning(
|
|
componentClass.getDefaultProps.isReactClassApproved,
|
|
'getDefaultProps is only used on classic React.createClass ' +
|
|
'definitions. Use a static property named `defaultProps` instead.'
|
|
);
|
|
}
|
|
}
|
|
|
|
return element;
|
|
},
|
|
|
|
createFactory: function(type) {
|
|
var validatedFactory = ReactElementValidator.createElement.bind(
|
|
null,
|
|
type
|
|
);
|
|
// Legacy hook TODO: Warn if this is accessed
|
|
validatedFactory.type = type;
|
|
return validatedFactory;
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = ReactElementValidator;
|