@samitouri / QOS-React-2 / commits / ca5d7bc683
Fork 0

The library for web and native user interfaces.

Add `getDefaultProps()`

As it turns out, default values are very useful. This implements getDefaultProps(), a hook for components to provide prop values when a prop is not specified by the user.

CommitSyncScript committed Jun 4, 2013 at 12:48 UTC ca5d7bc6831c0bfb1f37ff9bed02774e1caae5a7
2 files changed +91 -21
src/core/ReactCompositeComponent.js
+44 -21
@@ -91,6 +91,19 @@ var ReactCompositeComponentInterface = {
91
92 // ==== Definition methods ====
93
94 + /**
95 + * Invoked when the component is mounted and whenever new props are received.
96 + * Values in the returned mapping will be set on `this.props` if that prop is
97 + * not specified (i.e. using an `in` check).
98 + *
99 + * This method is invoked before `getInitialState` and therefore cannot rely
100 + * on `this.state` or use `this.setState`.
101 + *
102 + * @return {object}
103 + * @optional
104 + */
105 + getDefaultProps: SpecPolicy.DEFINE_ONCE,
106 +
107 /**
108 * Invoked once before the component is mounted. The return value will be used
109 * as the initial value of `this.state`.
@@ -419,9 +432,7 @@ var ReactCompositeComponentMixin = {
432 this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
433 this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
434
422 - if (this.constructor.propDeclarations) {
423 - this._assertValidProps(this.props);
424 - }
435 + this._processProps(this.props);
436
437 if (this.__reactAutoBindMap) {
438 this._bindAutoBindMethods();
@@ -489,9 +500,7 @@ var ReactCompositeComponentMixin = {
500 * @internal
501 */
502 receiveProps: function(nextProps, transaction) {
492 - if (this.constructor.propDeclarations) {
493 - this._assertValidProps(nextProps);
494 - }
503 + this._processProps(nextProps);
504 ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
505
506 this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
@@ -573,6 +582,35 @@ var ReactCompositeComponentMixin = {
582 }
583 },
584
585 + /**
586 + * Processes props by setting default values for unspecified props and
587 + * asserting that the props are valid.
588 + *
589 + * @param {object} props
590 + * @private
591 + */
592 + _processProps: function(props) {
593 + var propName;
594 + if (this.getDefaultProps) {
595 + var defaultProps = this.getDefaultProps();
596 + for (propName in defaultProps) {
597 + if (!(propName in props)) {
598 + props[propName] = defaultProps[propName];
599 + }
600 + }
601 + }
602 + var propDeclarations = this.constructor.propDeclarations;
603 + if (propDeclarations) {
604 + var componentName = this.constructor.displayName;
605 + for (propName in propDeclarations) {
606 + var checkProp = propDeclarations[propName];
607 + if (checkProp) {
608 + checkProp(props, propName, componentName);
609 + }
610 + }
611 + }
612 + },
613 +
614 /**
615 * Receives next props and next state, and negotiates whether or not the
616 * component should update as a result.
@@ -696,21 +734,6 @@ var ReactCompositeComponentMixin = {
734 return renderedComponent;
735 },
736
699 - /**
700 - * @param {object} props
701 - * @private
702 - */
703 - _assertValidProps: function(props) {
704 - var propDeclarations = this.constructor.propDeclarations;
705 - var componentName = this.constructor.displayName;
706 - for (var propName in propDeclarations) {
707 - var checkProp = propDeclarations[propName];
708 - if (checkProp) {
709 - checkProp(props, propName, componentName);
710 - }
711 - }
712 - },
713 -
737 /**
738 * @private
739 */
src/core/__tests__/ReactCompositeComponent-test.js
+47
@@ -194,6 +194,53 @@ describe('ReactCompositeComponent', function() {
194 expect(retValAfterMountWithCrazyScope).toBe(RETURN_VALUE_AFTER_MOUNT);
195 });
196
197 + it('should normalize props with default values', function() {
198 + var Component = React.createClass({
199 + props: {key: ReactProps.string.isRequired},
200 + getDefaultProps: function() {
201 + return {key: 'testKey'};
202 + },
203 + getInitialState: function() {
204 + return {key: this.props.key + 'State'};
205 + },
206 + render: function() {
207 + return <span>{this.props.key}</span>;
208 + }
209 + });
210 +
211 + var instance = <Component />;
212 + ReactTestUtils.renderIntoDocument(instance);
213 + reactComponentExpect(instance).scalarPropsEqual({key: 'testKey'});
214 + reactComponentExpect(instance).scalarStateEqual({key: 'testKeyState'});
215 +
216 + expect(function() {
217 + ReactTestUtils.renderIntoDocument(<Component key={null} />);
218 + }).toThrow(
219 + 'Invariant Violation: Required prop `key` was not specified in ' +
220 + '`Component`.'
221 + );
222 + });
223 +
224 + it('should check default prop values', function() {
225 + var Component = React.createClass({
226 + props: {key: ReactProps.string.isRequired},
227 + getDefaultProps: function() {
228 + return {key: null};
229 + },
230 + render: function() {
231 + return <span>{this.props.key}</span>;
232 + }
233 + });
234 +
235 + var instance = <Component />;
236 + expect(function() {
237 + ReactTestUtils.renderIntoDocument(instance);
238 + }).toThrow(
239 + 'Invariant Violation: Required prop `key` was not specified in ' +
240 + '`Component`.'
241 + );
242 + });
243 +
244 it('should check declared prop types', function() {
245 var Component = React.createClass({
246 props: {