diff --git a/docs/docs/05-reusable-components.md b/docs/docs/05-reusable-components.md index 89dfba5d555..8c9c236948e 100644 --- a/docs/docs/05-reusable-components.md +++ b/docs/docs/05-reusable-components.md @@ -187,5 +187,5 @@ React.renderComponent( ); ``` -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. +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. diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index daf90b59ed4..ad219d71aa3 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -34,6 +34,7 @@ var ReactUpdates = require('ReactUpdates'); var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); var keyMirror = require('keyMirror'); +var keyOf = require('keyOf'); var merge = require('merge'); var mixInto = require('mixInto'); var monitorCodeUse = require('monitorCodeUse'); @@ -41,6 +42,8 @@ var mapObject = require('mapObject'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var warning = require('warning'); +var MIXINS_KEY = keyOf({mixins: null}); + /** * Policies that describe methods in `ReactCompositeComponentInterface`. */ @@ -453,12 +456,25 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) { var Constructor = ConvenienceConstructor.type; var proto = Constructor.prototype; + + // By handling mixins before any other properties, we ensure the same + // chaining order is applied to methods with DEFINE_MANY policy, whether + // mixins are listed before or after these methods in the spec. + if (spec.hasOwnProperty(MIXINS_KEY)) { + RESERVED_SPEC_KEYS.mixins(ConvenienceConstructor, spec.mixins); + } + for (var name in spec) { - var property = spec[name]; if (!spec.hasOwnProperty(name)) { continue; } + if (name === MIXINS_KEY) { + // We have already handled mixins in a special case above + continue; + } + + var property = spec[name]; validateMethodOverride(proto, name); if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { diff --git a/src/core/__tests__/ReactCompositeComponentMixin-test.js b/src/core/__tests__/ReactCompositeComponentMixin-test.js index d77055f4ca1..e1b8cfa76b8 100644 --- a/src/core/__tests__/ReactCompositeComponentMixin-test.js +++ b/src/core/__tests__/ReactCompositeComponentMixin-test.js @@ -27,6 +27,7 @@ var reactComponentExpect; var TestComponent; var TestComponentWithPropTypes; +var TestComponentWithReverseSpec; var mixinPropValidator; var componentPropValidator; @@ -58,6 +59,13 @@ describe('ReactCompositeComponent-mixin', function() { } }; + var MixinBWithReverseSpec = { + componentDidMount: function() { + this.props.listener('MixinBWithReverseSpec didMount'); + }, + mixins: [MixinA] + }; + var MixinC = { statics: { staticC: function() {} @@ -89,6 +97,16 @@ describe('ReactCompositeComponent-mixin', function() { } }); + TestComponentWithReverseSpec = React.createClass({ + render: function() { + return
; + }, + componentDidMount: function() { + this.props.listener('Component didMount'); + }, + mixins: [MixinBWithReverseSpec, MixinC, MixinD] + }); + TestComponentWithPropTypes = React.createClass({ mixins: [MixinD], propTypes: { @@ -128,6 +146,19 @@ describe('ReactCompositeComponent-mixin', function() { ]); }); + it('should chain functions regardless of spec property order', function() { + var listener = mocks.getMockFunction(); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + + expect(listener.mock.calls).toEqual([ + ['MixinA didMount'], + ['MixinBWithReverseSpec didMount'], + ['MixinC didMount'], + ['Component didMount'] + ]); + }); + it('should validate prop types via mixins', function() { expect(TestComponent.type.propTypes).toBeDefined(); expect(TestComponent.type.propTypes.value)