@samitouri / QOS-React-2 / commits / 657602135c

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