Skip to content

Commit 04c9820

Browse files
committed
Merge pull request #1601 from gaearon/apply-component-and-mixin-spec-deterministically
Apply component and mixins specs deterministically
2 parents d970dc4 + 367c88e commit 04c9820

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

docs/docs/05-reusable-components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,5 @@ React.renderComponent(
190190
);
191191
```
192192

193-
A nice feature of mixins is that if a component is using multiple mixins and several mixins define the same lifecycle method (i.e. several mixins want to do some cleanup when the component is destroyed), all of the lifecycle methods are guaranteed to be called.
193+
A nice feature of mixins is that if a component is using multiple mixins and several mixins define the same lifecycle method (i.e. several mixins want to do some cleanup when the component is destroyed), all of the lifecycle methods are guaranteed to be called. Methods defined on mixins run in the order mixins were listed, followed by a method call on the component.
194194

src/core/ReactCompositeComponent.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ var ReactUpdates = require('ReactUpdates');
3636
var instantiateReactComponent = require('instantiateReactComponent');
3737
var invariant = require('invariant');
3838
var keyMirror = require('keyMirror');
39+
var keyOf = require('keyOf');
3940
var merge = require('merge');
4041
var mixInto = require('mixInto');
4142
var monitorCodeUse = require('monitorCodeUse');
4243
var mapObject = require('mapObject');
4344
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
4445
var warning = require('warning');
4546

47+
var MIXINS_KEY = keyOf({mixins: null});
48+
4649
/**
4750
* Policies that describe methods in `ReactCompositeComponentInterface`.
4851
*/
@@ -475,12 +478,25 @@ function mixSpecIntoComponent(Constructor, spec) {
475478
);
476479

477480
var proto = Constructor.prototype;
481+
482+
// By handling mixins before any other properties, we ensure the same
483+
// chaining order is applied to methods with DEFINE_MANY policy, whether
484+
// mixins are listed before or after these methods in the spec.
485+
if (spec.hasOwnProperty(MIXINS_KEY)) {
486+
RESERVED_SPEC_KEYS.mixins(ConvenienceConstructor, spec.mixins);
487+
}
488+
478489
for (var name in spec) {
479-
var property = spec[name];
480490
if (!spec.hasOwnProperty(name)) {
481491
continue;
482492
}
483493

494+
if (name === MIXINS_KEY) {
495+
// We have already handled mixins in a special case above
496+
continue;
497+
}
498+
499+
var property = spec[name];
484500
validateMethodOverride(proto, name);
485501

486502
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {

src/core/__tests__/ReactCompositeComponentMixin-test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var reactComponentExpect;
2727

2828
var TestComponent;
2929
var TestComponentWithPropTypes;
30+
var TestComponentWithReverseSpec;
3031
var mixinPropValidator;
3132
var componentPropValidator;
3233

@@ -58,6 +59,13 @@ describe('ReactCompositeComponent-mixin', function() {
5859
}
5960
};
6061

62+
var MixinBWithReverseSpec = {
63+
componentDidMount: function() {
64+
this.props.listener('MixinBWithReverseSpec didMount');
65+
},
66+
mixins: [MixinA]
67+
};
68+
6169
var MixinC = {
6270
statics: {
6371
staticC: function() {}
@@ -89,6 +97,16 @@ describe('ReactCompositeComponent-mixin', function() {
8997
}
9098
});
9199

100+
TestComponentWithReverseSpec = React.createClass({
101+
render: function() {
102+
return <div />;
103+
},
104+
componentDidMount: function() {
105+
this.props.listener('Component didMount');
106+
},
107+
mixins: [MixinBWithReverseSpec, MixinC, MixinD]
108+
});
109+
92110
TestComponentWithPropTypes = React.createClass({
93111
mixins: [MixinD],
94112
propTypes: {
@@ -128,6 +146,19 @@ describe('ReactCompositeComponent-mixin', function() {
128146
]);
129147
});
130148

149+
it('should chain functions regardless of spec property order', function() {
150+
var listener = mocks.getMockFunction();
151+
var instance = <TestComponentWithReverseSpec listener={listener} />;
152+
instance = ReactTestUtils.renderIntoDocument(instance);
153+
154+
expect(listener.mock.calls).toEqual([
155+
['MixinA didMount'],
156+
['MixinBWithReverseSpec didMount'],
157+
['MixinC didMount'],
158+
['Component didMount']
159+
]);
160+
});
161+
131162
it('should validate prop types via mixins', function() {
132163
expect(TestComponent.type.propTypes).toBeDefined();
133164
expect(TestComponent.type.propTypes.value)

0 commit comments

Comments
 (0)