diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js
index 914d3846e1..b66cf4900e 100644
--- a/src/core/ReactCompositeComponent.js
+++ b/src/core/ReactCompositeComponent.js
@@ -679,7 +679,7 @@ var ReactCompositeComponentMixin = {
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
- this._processProps(this.props);
+ this.props = this._processProps(this.props);
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
@@ -853,12 +853,15 @@ var ReactCompositeComponentMixin = {
/**
* Processes props by setting default values for unspecified props and
- * asserting that the props are valid.
+ * asserting that the props are valid. Does not mutate its argument; returns
+ * a new props object with defaults merged in.
*
- * @param {object} props
+ * @param {object} newProps
+ * @return {object}
* @private
*/
- _processProps: function(props) {
+ _processProps: function(newProps) {
+ var props = merge(newProps);
var defaultProps = this._defaultProps;
for (var propName in defaultProps) {
if (typeof props[propName] === 'undefined') {
@@ -869,6 +872,7 @@ var ReactCompositeComponentMixin = {
if (propTypes) {
this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop);
}
+ return props;
},
/**
@@ -920,8 +924,7 @@ var ReactCompositeComponentMixin = {
var nextProps = this.props;
if (this._pendingProps != null) {
- nextProps = this._pendingProps;
- this._processProps(nextProps);
+ nextProps = this._processProps(this._pendingProps);
this._pendingProps = null;
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js
index 6866bcd2b2..1f2f8dd52b 100644
--- a/src/core/__tests__/ReactCompositeComponent-test.js
+++ b/src/core/__tests__/ReactCompositeComponent-test.js
@@ -213,6 +213,47 @@ describe('ReactCompositeComponent', function() {
reactComponentExpect(instance3).scalarPropsEqual({key: null});
});
+ it('should not mutate passed-in props object', function() {
+ var Component = React.createClass({
+ getDefaultProps: function() {
+ return {key: 'testKey'};
+ },
+ render: function() {
+ return ;
+ }
+ });
+
+ var inputProps = {};
+ var instance1 = Component(inputProps);
+ ReactTestUtils.renderIntoDocument(instance1);
+ expect(instance1.props.key).toBe('testKey');
+
+ // We don't mutate the input, just in case the caller wants to do something
+ // with it after using it to instantiate a component
+ expect(inputProps.key).not.toBeDefined();
+ });
+
+ it('should use default prop value when removing a key', function() {
+ var Component = React.createClass({
+ getDefaultProps: function() {
+ return {fruit: 'persimmon'};
+ },
+ render: function() {
+ return ;
+ }
+ });
+
+ var container = document.createElement('div');
+ var instance = React.renderComponent(
+ ,
+ container
+ );
+ expect(instance.props.fruit).toBe('mango');
+
+ React.renderComponent(, container);
+ expect(instance.props.fruit).toBe('persimmon');
+ });
+
it('should normalize props with default values', function() {
var Component = React.createClass({
propTypes: {key: ReactPropTypes.string.isRequired},