Don't mutate passed-in props, take 2
Ben Alpert committed
Dec 5, 2013 at 16:59 UTC
657602135ce8a661aa1d60b338b3483d3ab505ff
2 files changed
+50
-6
src/core/ReactCompositeComponent.js
+9
-6
@@ -679,7 +679,7 @@ var ReactCompositeComponentMixin = {
679
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
680
681
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
682
- this._processProps(this.props);
682
+ this.props = this._processProps(this.props);
683
684
if (this.__reactAutoBindMap) {
685
this._bindAutoBindMethods();
@@ -853,12 +853,15 @@ var ReactCompositeComponentMixin = {
853
854
/**
855
* Processes props by setting default values for unspecified props and
856
- * asserting that the props are valid.
856
+ * asserting that the props are valid. Does not mutate its argument; returns
857
+ * a new props object with defaults merged in.
858
*
858
- * @param {object} props
859
+ * @param {object} newProps
860
+ * @return {object}
861
* @private
862
*/
861
- _processProps: function(props) {
863
+ _processProps: function(newProps) {
864
+ var props = merge(newProps);
865
var defaultProps = this._defaultProps;
866
for (var propName in defaultProps) {
867
if (typeof props[propName] === 'undefined') {
@@ -869,6 +872,7 @@ var ReactCompositeComponentMixin = {
872
if (propTypes) {
873
this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop);
874
}
875
+ return props;
876
},
877
878
/**
@@ -920,8 +924,7 @@ var ReactCompositeComponentMixin = {
924
925
var nextProps = this.props;
926
if (this._pendingProps != null) {
923
- nextProps = this._pendingProps;
924
- this._processProps(nextProps);
927
+ nextProps = this._processProps(this._pendingProps);
928
this._pendingProps = null;
929
930
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
src/core/__tests__/ReactCompositeComponent-test.js
+41
@@ -213,6 +213,47 @@ describe('ReactCompositeComponent', function() {
213
reactComponentExpect(instance3).scalarPropsEqual({key: null});
214
});
215
216
+ it('should not mutate passed-in props object', function() {
217
+ var Component = React.createClass({
218
+ getDefaultProps: function() {
219
+ return {key: 'testKey'};
220
+ },
221
+ render: function() {
222
+ return <span />;
223
+ }
224
+ });
225
+
226
+ var inputProps = {};
227
+ var instance1 = Component(inputProps);
228
+ ReactTestUtils.renderIntoDocument(instance1);
229
+ expect(instance1.props.key).toBe('testKey');
230
+
231
+ // We don't mutate the input, just in case the caller wants to do something
232
+ // with it after using it to instantiate a component
233
+ expect(inputProps.key).not.toBeDefined();
234
+ });
235
+
236
+ it('should use default prop value when removing a key', function() {
237
+ var Component = React.createClass({
238
+ getDefaultProps: function() {
239
+ return {fruit: 'persimmon'};
240
+ },
241
+ render: function() {
242
+ return <span />;
243
+ }
244
+ });
245
+
246
+ var container = document.createElement('div');
247
+ var instance = React.renderComponent(
248
+ <Component fruit="mango" />,
249
+ container
250
+ );
251
+ expect(instance.props.fruit).toBe('mango');
252
+
253
+ React.renderComponent(<Component />, container);
254
+ expect(instance.props.fruit).toBe('persimmon');
255
+ });
256
+
257
it('should normalize props with default values', function() {
258
var Component = React.createClass({
259
propTypes: {key: ReactPropTypes.string.isRequired},