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},