diff --git a/addons/create-react-class/factory.js b/addons/create-react-class/factory.js index c202f0b916..830c414815 100644 --- a/addons/create-react-class/factory.js +++ b/addons/create-react-class/factory.js @@ -270,6 +270,27 @@ function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { */ componentWillUnmount: 'DEFINE_MANY', + /** + * Replacement for (deprecated) `componentWillMount`. + * + * @optional + */ + UNSAFE_componentWillMount: 'DEFINE_MANY', + + /** + * Replacement for (deprecated) `componentWillReceiveProps`. + * + * @optional + */ + UNSAFE_componentWillReceiveProps: 'DEFINE_MANY', + + /** + * Replacement for (deprecated) `componentWillUpdate`. + * + * @optional + */ + UNSAFE_componentWillUpdate: 'DEFINE_MANY', + // ==== Advanced methods ==== /** @@ -285,6 +306,23 @@ function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { updateComponent: 'OVERRIDE_BASE' }; + /** + * Similar to ReactClassInterface but for static methods. + */ + var ReactClassStaticInterface = { + /** + * This method is invoked after a component is instantiated and when it + * receives new props. Return an object to update state in response to + * prop changes. Return null to indicate no change to state. + * + * If an object is returned, its keys will be merged into the existing state. + * + * @return {object || null} + * @optional + */ + getDerivedStateFromProps: 'DEFINE_MANY_MERGED' + }; + /** * Mapping from class specification keys to special processing functions. * @@ -519,6 +557,7 @@ function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { if (!statics) { return; } + for (var name in statics) { var property = statics[name]; if (!statics.hasOwnProperty(name)) { @@ -535,14 +574,25 @@ function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { name ); - var isInherited = name in Constructor; - _invariant( - !isInherited, - 'ReactClass: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be ' + - 'due to a mixin.', - name - ); + var isAlreadyDefined = name in Constructor; + if (isAlreadyDefined) { + var specPolicy = ReactClassStaticInterface.hasOwnProperty(name) + ? ReactClassStaticInterface[name] + : null; + + _invariant( + specPolicy === 'DEFINE_MANY_MERGED', + 'ReactClass: You are attempting to define ' + + '`%s` on your component more than once. This conflict may be ' + + 'due to a mixin.', + name + ); + + Constructor[name] = createMergedResultFunction(Constructor[name], property); + + return; + } + Constructor[name] = property; } } @@ -852,6 +902,12 @@ function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component' ); + warning( + !Constructor.prototype.UNSAFE_componentWillRecieveProps, + '%s has a method called UNSAFE_componentWillRecieveProps(). ' + + 'Did you mean UNSAFE_componentWillReceiveProps()?', + spec.displayName || 'A component' + ); } // Reduce time spent doing lookups by setting these on the prototype. diff --git a/addons/create-react-class/package.json b/addons/create-react-class/package.json index 3c4ddf6c82..5c6d19c019 100644 --- a/addons/create-react-class/package.json +++ b/addons/create-react-class/package.json @@ -1,6 +1,6 @@ { "name": "create-react-class", - "version": "15.6.2", + "version": "15.6.3", "description": "Legacy API for creating React components.", "main": "index.js", "license": "MIT", diff --git a/addons/create-react-class/test.js b/addons/create-react-class/test.js index 5218628c0b..4273171860 100644 --- a/addons/create-react-class/test.js +++ b/addons/create-react-class/test.js @@ -522,4 +522,55 @@ describe('ReactClass-spec', () => { 'to prevent memory leaks.' ); }); + + it('should support getInitialState mixin', () => { + const Component = createReactClass({ + mixins: [{ + getInitialState: function(props) { + return { + foo: 'foo' + }; + }, + }], + getInitialState: function(props) { + return { + bar: 'bar' + }; + }, + render: function() { + return
; + } + }); + const instance = renderIntoDocument(); + expect(instance.state.foo).toEqual('foo'); + expect(instance.state.bar).toEqual('bar'); + }); + + it('should merge return values for static getDerivedStateFromProps mixin', () => { + const Component = createReactClass({ + mixins: [{ + statics: { + getDerivedStateFromProps: function(props, prevState) { + return { + foo: 'foo' + }; + } + }, + }], + statics: { + getDerivedStateFromProps: function(props, prevState) { + return { + bar: 'bar' + }; + } + }, + render: function() { + return
; + } + }); + + const state = Component.getDerivedStateFromProps(); + expect(state.foo).toEqual('foo'); + expect(state.bar).toEqual('bar'); + }); });