diff --git a/src/browser/ui/React.js b/src/browser/ui/React.js
index 6215fe3434..5232085661 100644
--- a/src/browser/ui/React.js
+++ b/src/browser/ui/React.js
@@ -39,10 +39,12 @@ ReactDefaultInjection.inject();
var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
+var cloneElement = ReactElement.cloneElement;
if (__DEV__) {
createElement = ReactElementValidator.createElement;
createFactory = ReactElementValidator.createFactory;
+ cloneElement = ReactElementValidator.cloneElement;
}
var render = ReactPerf.measure('React', 'render', ReactMount.render);
@@ -62,6 +64,7 @@ var React = {
},
createClass: ReactClass.createClass,
createElement: createElement,
+ cloneElement: cloneElement,
createFactory: createFactory,
createMixin: function(mixin) {
// Currently a noop. Will be used to validate and trace mixins.
diff --git a/src/classic/element/ReactElement.js b/src/classic/element/ReactElement.js
index c582f95a6a..13313ae418 100644
--- a/src/classic/element/ReactElement.js
+++ b/src/classic/element/ReactElement.js
@@ -228,6 +228,60 @@ ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
return newElement;
};
+ReactElement.cloneElement = function(element, config, children) {
+ var propName;
+
+ // Original props are copied
+ var props = assign({}, element.props);
+
+ // Reserved names are extracted
+ var key = element.key;
+ var ref = element.ref;
+
+ // Owner will be preserved, unless ref is overridden
+ var owner = element._owner;
+
+ if (config != null) {
+ if (config.ref !== undefined) {
+ // Silently steal the ref from the parent.
+ ref = config.ref;
+ owner = ReactCurrentOwner.current;
+ }
+ if (config.key !== undefined) {
+ key = '' + config.key;
+ }
+ // Remaining properties override existing props
+ for (propName in config) {
+ if (config.hasOwnProperty(propName) &&
+ !RESERVED_PROPS.hasOwnProperty(propName)) {
+ props[propName] = config[propName];
+ }
+ }
+ }
+
+ // Children can be more than one argument, and those are transferred onto
+ // the newly allocated props object.
+ var childrenLength = arguments.length - 2;
+ if (childrenLength === 1) {
+ props.children = children;
+ } else if (childrenLength > 1) {
+ var childArray = Array(childrenLength);
+ for (var i = 0; i < childrenLength; i++) {
+ childArray[i] = arguments[i + 2];
+ }
+ props.children = childArray;
+ }
+
+ return new ReactElement(
+ element.type,
+ key,
+ ref,
+ owner,
+ element._context,
+ props
+ );
+};
+
/**
* @param {?object} object
* @return {boolean} True if `object` is a valid component.
diff --git a/src/classic/element/ReactElementValidator.js b/src/classic/element/ReactElementValidator.js
index 35cd38d6cf..1f25c23d42 100644
--- a/src/classic/element/ReactElementValidator.js
+++ b/src/classic/element/ReactElementValidator.js
@@ -336,6 +336,42 @@ function checkAndWarnForMutatedProps(element) {
}
}
+/**
+ * Given an element, validate that its props follow the propTypes definition,
+ * provided by the type.
+ *
+ * @param {ReactElement} element
+ */
+function validatePropTypes(element) {
+ if (element.type == null) {
+ // This has already warned. Don't throw.
+ return;
+ }
+ // 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 (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.'
+ );
+ }
+}
+
var ReactElementValidator = {
checkAndWarnForMutatedProps: checkAndWarnForMutatedProps,
@@ -362,33 +398,7 @@ var ReactElementValidator = {
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.'
- );
- }
- }
+ validatePropTypes(element);
return element;
},
@@ -428,6 +438,15 @@ var ReactElementValidator = {
return validatedFactory;
+ },
+
+ cloneElement: function(element, props, children) {
+ var newElement = ReactElement.cloneElement.apply(this, arguments);
+ for (var i = 2; i < arguments.length; i++) {
+ validateChildKeys(arguments[i], newElement.type);
+ }
+ validatePropTypes(newElement);
+ return newElement;
}
};
diff --git a/src/classic/element/__tests__/ReactElementClone-test.js b/src/classic/element/__tests__/ReactElementClone-test.js
new file mode 100644
index 0000000000..66b086c341
--- /dev/null
+++ b/src/classic/element/__tests__/ReactElementClone-test.js
@@ -0,0 +1,268 @@
+/**
+ * Copyright 2013-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.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+require('mock-modules');
+
+var mocks = require('mocks');
+
+var React;
+var ReactTestUtils;
+
+describe('ReactElementClone', function() {
+
+ beforeEach(function() {
+ React = require('React');
+ ReactTestUtils = require('ReactTestUtils');
+ });
+
+ it('should clone a DOM component with new props', function() {
+ var Grandparent = React.createClass({
+ render: function() {
+ return