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: {