diff --git a/addons/react-create-class/factory.js b/addons/react-create-class/factory.js index 5b5d06ee6ae7..e7aedf0d664a 100644 --- a/addons/react-create-class/factory.js +++ b/addons/react-create-class/factory.js @@ -13,27 +13,32 @@ var _assign = require('object-assign'); var emptyObject = require('fbjs/lib/emptyObject'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) { - var ReactPropTypeLocationNames = {}; - if (process.env.NODE_ENV !== 'production') { - ReactPropTypeLocationNames = { - prop: 'prop', - context: 'context', - childContext: 'child context' - }; - } - - var MIXINS_KEY = 'mixins'; - - // Helper function to allow the creation of anonymous functions which do not - // have .name set to the name of the variable being assigned to. - function identity(fn) { - return fn; - } +var _invariant = require('fbjs/lib/invariant'); + +if (process.env.NODE_ENV !== 'production') { + var warning = require('fbjs/lib/warning'); +} + +var MIXINS_KEY = 'mixins'; + +// Helper function to allow the creation of anonymous functions which do not +// have .name set to the name of the variable being assigned to. +function identity(fn) { + return fn; +} + +var ReactPropTypeLocationNames; +if (process.env.NODE_ENV !== 'production') { + ReactPropTypeLocationNames = { + prop: 'prop', + context: 'context', + childContext: 'child context', + }; +} else { + ReactPropTypeLocationNames = {}; +} +function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { /** * Policies that describe methods in `ReactClassInterface`. */ @@ -343,7 +348,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) function validateTypeDef(Constructor, typeDef, location) { for (var propName in typeDef) { if (typeDef.hasOwnProperty(propName)) { - // use a warning instead of an invariant so components + // use a warning instead of an _invariant so components // don't show up in prod but only in __DEV__ process.env.NODE_ENV !== 'production' ? warning(typeof typeDef[propName] === 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'React.PropTypes.', Constructor.displayName || 'ReactClass', ReactPropTypeLocationNames[location], propName) : void 0; } @@ -355,12 +360,12 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) // Disallow overriding of base class methods unless explicitly allowed. if (ReactClassMixin.hasOwnProperty(name)) { - invariant(specPolicy === 'OVERRIDE_BASE', 'ReactClassInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name); + _invariant(specPolicy === 'OVERRIDE_BASE', 'ReactClassInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name); } // Disallow defining methods more than once unless explicitly allowed. if (isAlreadyDefined) { - invariant(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', 'ReactClassInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name); + _invariant(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', 'ReactClassInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name); } } @@ -380,8 +385,8 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) return; } - invariant(typeof spec !== 'function', 'ReactClass: You\'re attempting to ' + 'use a component class or function as a mixin. Instead, just use a ' + 'regular object.'); - invariant(!isValidElement(spec), 'ReactClass: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.'); + _invariant(typeof spec !== 'function', 'ReactClass: You\'re attempting to ' + 'use a component class or function as a mixin. Instead, just use a ' + 'regular object.'); + _invariant(!isValidElement(spec), 'ReactClass: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.'); var proto = Constructor.prototype; var autoBindPairs = proto.__reactAutoBindPairs; @@ -426,7 +431,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) var specPolicy = ReactClassInterface[name]; // These cases should already be caught by validateMethodOverride. - invariant(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY'), 'ReactClass: Unexpected spec policy %s for key %s ' + 'when mixing in component specs.', specPolicy, name); + _invariant(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY'), 'ReactClass: Unexpected spec policy %s for key %s ' + 'when mixing in component specs.', specPolicy, name); // For methods which are defined more than once, call the existing // methods before calling the new property, merging if appropriate. @@ -461,10 +466,10 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } var isReserved = name in RESERVED_SPEC_KEYS; - invariant(!isReserved, 'ReactClass: You are attempting to define a reserved ' + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 'as an instance property instead; it will still be accessible on the ' + 'constructor.', name); + _invariant(!isReserved, 'ReactClass: You are attempting to define a reserved ' + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 'as an instance property instead; it will still be accessible on the ' + 'constructor.', 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); + _invariant(!isInherited, 'ReactClass: You are attempting to define ' + '`%s` on your component more than once. This conflict may be ' + 'due to a mixin.', name); Constructor[name] = property; } } @@ -477,11 +482,11 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * @return {object} one after it has been mutated to contain everything in two. */ function mergeIntoWithNoDuplicateKeys(one, two) { - invariant(one && two && typeof one === 'object' && typeof two === 'object', 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'); + _invariant(one && two && typeof one === 'object' && typeof two === 'object', 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'); for (var key in two) { if (two.hasOwnProperty(key)) { - invariant(one[key] === undefined, 'mergeIntoWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: `%s`. This conflict ' + 'may be due to a mixin; in particular, this may be caused by two ' + 'getInitialState() or getDefaultProps() methods returning objects ' + 'with clashing keys.', key); + _invariant(one[key] === undefined, 'mergeIntoWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: `%s`. This conflict ' + 'may be due to a mixin; in particular, this may be caused by two ' + 'getInitialState() or getDefaultProps() methods returning objects ' + 'with clashing keys.', key); one[key] = two[key]; } } @@ -580,6 +585,15 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } } + var IsMountedMixin = { + componentDidMount: function () { + this.__isMounted = true; + }, + componentWillUnmount: function () { + this.__isMounted = false; + } + }; + /** * Add more to the ReactClass base class. These are all legacy features and * therefore not already part of the modern ReactComponent. @@ -591,10 +605,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * type signature and the only use case for this, is to avoid that. */ replaceState: function (newState, callback) { - this.updater.enqueueReplaceState(this, newState); - if (callback) { - this.updater.enqueueCallback(this, callback, 'replaceState'); - } + this.updater.enqueueReplaceState(this, newState, callback); }, /** @@ -604,7 +615,11 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * @final */ isMounted: function () { - return this.updater.isMounted(this); + if (process.env.NODE_ENV !== 'production') { + process.env.NODE_ENV !== 'production' ? warning(this.__didWarnIsMounted, '%s: isMounted is deprecated. Instead, make sure to clean up ' + 'subscriptions and pending requests in componentWillUnmount to ' + 'prevent memory leaks.', this.constructor && this.constructor.displayName || this.name || 'Component') : void 0; + this.__didWarnIsMounted = true; + } + return !!this.__isMounted; } }; @@ -655,7 +670,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) initialState = null; } } - invariant(typeof initialState === 'object' && !Array.isArray(initialState), '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent'); + _invariant(typeof initialState === 'object' && !Array.isArray(initialState), '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent'); this.state = initialState; }); @@ -665,6 +680,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); + mixSpecIntoComponent(Constructor, IsMountedMixin); mixSpecIntoComponent(Constructor, spec); // Initialize the defaultProps property after all mixins have been merged. @@ -685,7 +701,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } } - invariant(Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.'); + _invariant(Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.'); if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : void 0; @@ -703,4 +719,6 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } return createClass; -}; +} + +module.exports = factory; diff --git a/addons/react-create-class/package.json b/addons/react-create-class/package.json index 273cd51ca544..b5af764703b2 100644 --- a/addons/react-create-class/package.json +++ b/addons/react-create-class/package.json @@ -1,6 +1,6 @@ { "name": "react-create-class", - "version": "15.5.0-alpha.3", + "version": "15.5.0-alpha.7", "description": "Deprecated, legacy API for creating React components.", "main": "index.js", "license": "BSD-3-Clause", diff --git a/addons/react-create-class/react-create-class.js b/addons/react-create-class/react-create-class.js index 6d3f9c42666b..4851c461d503 100644 --- a/addons/react-create-class/react-create-class.js +++ b/addons/react-create-class/react-create-class.js @@ -44,27 +44,32 @@ var _assign = require('object-assign'); var emptyObject = require('fbjs/lib/emptyObject'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) { - var ReactPropTypeLocationNames = {}; - if (process.env.NODE_ENV !== 'production') { - ReactPropTypeLocationNames = { - prop: 'prop', - context: 'context', - childContext: 'child context' - }; - } +var _invariant = require('fbjs/lib/invariant'); + +if (process.env.NODE_ENV !== 'production') { + var warning = require('fbjs/lib/warning'); +} - var MIXINS_KEY = 'mixins'; +var MIXINS_KEY = 'mixins'; - // Helper function to allow the creation of anonymous functions which do not - // have .name set to the name of the variable being assigned to. - function identity(fn) { - return fn; - } +// Helper function to allow the creation of anonymous functions which do not +// have .name set to the name of the variable being assigned to. +function identity(fn) { + return fn; +} + +var ReactPropTypeLocationNames; +if (process.env.NODE_ENV !== 'production') { + ReactPropTypeLocationNames = { + prop: 'prop', + context: 'context', + childContext: 'child context', + }; +} else { + ReactPropTypeLocationNames = {}; +} +function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { /** * Policies that describe methods in `ReactClassInterface`. */ @@ -374,7 +379,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) function validateTypeDef(Constructor, typeDef, location) { for (var propName in typeDef) { if (typeDef.hasOwnProperty(propName)) { - // use a warning instead of an invariant so components + // use a warning instead of an _invariant so components // don't show up in prod but only in __DEV__ process.env.NODE_ENV !== 'production' ? warning(typeof typeDef[propName] === 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'React.PropTypes.', Constructor.displayName || 'ReactClass', ReactPropTypeLocationNames[location], propName) : void 0; } @@ -386,12 +391,12 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) // Disallow overriding of base class methods unless explicitly allowed. if (ReactClassMixin.hasOwnProperty(name)) { - invariant(specPolicy === 'OVERRIDE_BASE', 'ReactClassInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name); + _invariant(specPolicy === 'OVERRIDE_BASE', 'ReactClassInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name); } // Disallow defining methods more than once unless explicitly allowed. if (isAlreadyDefined) { - invariant(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', 'ReactClassInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name); + _invariant(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', 'ReactClassInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name); } } @@ -411,8 +416,8 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) return; } - invariant(typeof spec !== 'function', 'ReactClass: You\'re attempting to ' + 'use a component class or function as a mixin. Instead, just use a ' + 'regular object.'); - invariant(!isValidElement(spec), 'ReactClass: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.'); + _invariant(typeof spec !== 'function', 'ReactClass: You\'re attempting to ' + 'use a component class or function as a mixin. Instead, just use a ' + 'regular object.'); + _invariant(!isValidElement(spec), 'ReactClass: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.'); var proto = Constructor.prototype; var autoBindPairs = proto.__reactAutoBindPairs; @@ -457,7 +462,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) var specPolicy = ReactClassInterface[name]; // These cases should already be caught by validateMethodOverride. - invariant(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY'), 'ReactClass: Unexpected spec policy %s for key %s ' + 'when mixing in component specs.', specPolicy, name); + _invariant(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY'), 'ReactClass: Unexpected spec policy %s for key %s ' + 'when mixing in component specs.', specPolicy, name); // For methods which are defined more than once, call the existing // methods before calling the new property, merging if appropriate. @@ -492,10 +497,10 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } var isReserved = name in RESERVED_SPEC_KEYS; - invariant(!isReserved, 'ReactClass: You are attempting to define a reserved ' + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 'as an instance property instead; it will still be accessible on the ' + 'constructor.', name); + _invariant(!isReserved, 'ReactClass: You are attempting to define a reserved ' + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 'as an instance property instead; it will still be accessible on the ' + 'constructor.', 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); + _invariant(!isInherited, 'ReactClass: You are attempting to define ' + '`%s` on your component more than once. This conflict may be ' + 'due to a mixin.', name); Constructor[name] = property; } } @@ -508,11 +513,11 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * @return {object} one after it has been mutated to contain everything in two. */ function mergeIntoWithNoDuplicateKeys(one, two) { - invariant(one && two && typeof one === 'object' && typeof two === 'object', 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'); + _invariant(one && two && typeof one === 'object' && typeof two === 'object', 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'); for (var key in two) { if (two.hasOwnProperty(key)) { - invariant(one[key] === undefined, 'mergeIntoWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: `%s`. This conflict ' + 'may be due to a mixin; in particular, this may be caused by two ' + 'getInitialState() or getDefaultProps() methods returning objects ' + 'with clashing keys.', key); + _invariant(one[key] === undefined, 'mergeIntoWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: `%s`. This conflict ' + 'may be due to a mixin; in particular, this may be caused by two ' + 'getInitialState() or getDefaultProps() methods returning objects ' + 'with clashing keys.', key); one[key] = two[key]; } } @@ -611,6 +616,15 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } } + var IsMountedMixin = { + componentDidMount: function () { + this.__isMounted = true; + }, + componentWillUnmount: function () { + this.__isMounted = false; + } + }; + /** * Add more to the ReactClass base class. These are all legacy features and * therefore not already part of the modern ReactComponent. @@ -622,10 +636,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * type signature and the only use case for this, is to avoid that. */ replaceState: function (newState, callback) { - this.updater.enqueueReplaceState(this, newState); - if (callback) { - this.updater.enqueueCallback(this, callback, 'replaceState'); - } + this.updater.enqueueReplaceState(this, newState, callback); }, /** @@ -635,7 +646,11 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) * @final */ isMounted: function () { - return this.updater.isMounted(this); + if (process.env.NODE_ENV !== 'production') { + process.env.NODE_ENV !== 'production' ? warning(this.__didWarnIsMounted, '%s: isMounted is deprecated. Instead, make sure to clean up ' + 'subscriptions and pending requests in componentWillUnmount to ' + 'prevent memory leaks.', this.constructor && this.constructor.displayName || this.name || 'Component') : void 0; + this.__didWarnIsMounted = true; + } + return !!this.__isMounted; } }; @@ -686,7 +701,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) initialState = null; } } - invariant(typeof initialState === 'object' && !Array.isArray(initialState), '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent'); + _invariant(typeof initialState === 'object' && !Array.isArray(initialState), '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent'); this.state = initialState; }); @@ -696,6 +711,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); + mixSpecIntoComponent(Constructor, IsMountedMixin); mixSpecIntoComponent(Constructor, spec); // Initialize the defaultProps property after all mixins have been merged. @@ -716,7 +732,7 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } } - invariant(Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.'); + _invariant(Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.'); if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : void 0; @@ -734,7 +750,9 @@ module.exports = function(ReactComponent, isValidElement, ReactNoopUpdateQueue) } return createClass; -}; +} + +module.exports = factory; }).call(this,require('_process')) },{"_process":8,"fbjs/lib/emptyObject":4,"fbjs/lib/invariant":5,"fbjs/lib/warning":6,"object-assign":7}],2:[function(require,module,exports){ @@ -761,7 +779,7 @@ module.exports = factory( ReactNoopUpdateQueue ); -},{"./factory":1}],3:[function(require,module,exports){ +},{"./factory":1,"react":"react"}],3:[function(require,module,exports){ "use strict"; /** diff --git a/addons/react-create-class/react-create-class.min.js b/addons/react-create-class/react-create-class.min.js index c17b35aead43..267d891a6b02 100644 --- a/addons/react-create-class/react-create-class.min.js +++ b/addons/react-create-class/react-create-class.min.js @@ -1 +1 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}if(typeof g.React==="undefined"){throw Error("React module should be required before createClass")}g.ReactCreateClass=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key]}if(newThis!==component&&newThis!==null){process.env.NODE_ENV!=="production"?warning(false,"bind(): React component methods may only be bound to the "+"component instance. See %s",componentName):void 0}else if(!args.length){process.env.NODE_ENV!=="production"?warning(false,"bind(): You are binding a component method to the component. "+"React does this for you automatically in a high-performance "+"way, so you can safely remove this call. See %s",componentName):void 0;return boundMethod}var reboundMethod=_bind.apply(boundMethod,arguments);reboundMethod.__reactBoundContext=component;reboundMethod.__reactBoundMethod=method;reboundMethod.__reactBoundArguments=args;return reboundMethod}}return boundMethod}function bindAutoBindMethods(component){var pairs=component.__reactAutoBindPairs;for(var i=0;i1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key]}var argIndex=0;var message="Warning: "+format.replace(/%s/g,function(){return args[argIndex++]});if(typeof console!=="undefined"){console.error(message)}try{throw new Error(message)}catch(x){}};warning=function warning(condition,format){if(format===undefined){throw new Error("`warning(condition, format, ...args)` requires a warning "+"message argument")}if(format.indexOf("Failed Composite propType: ")===0){return}if(!condition){for(var _len2=arguments.length,args=Array(_len2>2?_len2-2:0),_key2=2;_key2<_len2;_key2++){args[_key2-2]=arguments[_key2]}printWarning.apply(undefined,[format].concat(args))}}})()}module.exports=warning}).call(this,require("_process"))},{"./emptyFunction":3,_process:8}],7:[function(require,module,exports){"use strict";var getOwnPropertySymbols=Object.getOwnPropertySymbols;var hasOwnProperty=Object.prototype.hasOwnProperty;var propIsEnumerable=Object.prototype.propertyIsEnumerable;function toObject(val){if(val===null||val===undefined){throw new TypeError("Object.assign cannot be called with null or undefined")}return Object(val)}function shouldUseNative(){try{if(!Object.assign){return false}var test1=new String("abc");test1[5]="de";if(Object.getOwnPropertyNames(test1)[0]==="5"){return false}var test2={};for(var i=0;i<10;i++){test2["_"+String.fromCharCode(i)]=i}var order2=Object.getOwnPropertyNames(test2).map(function(n){return test2[n]});if(order2.join("")!=="0123456789"){return false}var test3={};"abcdefghijklmnopqrst".split("").forEach(function(letter){test3[letter]=letter});if(Object.keys(Object.assign({},test3)).join("")!=="abcdefghijklmnopqrst"){return false}return true}catch(err){return false}}module.exports=shouldUseNative()?Object.assign:function(target,source){var from;var to=toObject(target);var symbols;for(var s=1;s1){for(var i=1;i1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key]}if(newThis!==component&&newThis!==null){process.env.NODE_ENV!=="production"?warning(false,"bind(): React component methods may only be bound to the "+"component instance. See %s",componentName):void 0}else if(!args.length){process.env.NODE_ENV!=="production"?warning(false,"bind(): You are binding a component method to the component. "+"React does this for you automatically in a high-performance "+"way, so you can safely remove this call. See %s",componentName):void 0;return boundMethod}var reboundMethod=_bind.apply(boundMethod,arguments);reboundMethod.__reactBoundContext=component;reboundMethod.__reactBoundMethod=method;reboundMethod.__reactBoundArguments=args;return reboundMethod}}return boundMethod}function bindAutoBindMethods(component){var pairs=component.__reactAutoBindPairs;for(var i=0;i1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key]}var argIndex=0;var message="Warning: "+format.replace(/%s/g,function(){return args[argIndex++]});if(typeof console!=="undefined"){console.error(message)}try{throw new Error(message)}catch(x){}};warning=function warning(condition,format){if(format===undefined){throw new Error("`warning(condition, format, ...args)` requires a warning "+"message argument")}if(format.indexOf("Failed Composite propType: ")===0){return}if(!condition){for(var _len2=arguments.length,args=Array(_len2>2?_len2-2:0),_key2=2;_key2<_len2;_key2++){args[_key2-2]=arguments[_key2]}printWarning.apply(undefined,[format].concat(args))}}})()}module.exports=warning}).call(this,require("_process"))},{"./emptyFunction":3,_process:8}],7:[function(require,module,exports){"use strict";var getOwnPropertySymbols=Object.getOwnPropertySymbols;var hasOwnProperty=Object.prototype.hasOwnProperty;var propIsEnumerable=Object.prototype.propertyIsEnumerable;function toObject(val){if(val===null||val===undefined){throw new TypeError("Object.assign cannot be called with null or undefined")}return Object(val)}function shouldUseNative(){try{if(!Object.assign){return false}var test1=new String("abc");test1[5]="de";if(Object.getOwnPropertyNames(test1)[0]==="5"){return false}var test2={};for(var i=0;i<10;i++){test2["_"+String.fromCharCode(i)]=i}var order2=Object.getOwnPropertyNames(test2).map(function(n){return test2[n]});if(order2.join("")!=="0123456789"){return false}var test3={};"abcdefghijklmnopqrst".split("").forEach(function(letter){test3[letter]=letter});if(Object.keys(Object.assign({},test3)).join("")!=="abcdefghijklmnopqrst"){return false}return true}catch(err){return false}}module.exports=shouldUseNative()?Object.assign:function(target,source){var from;var to=toObject(target);var symbols;for(var s=1;s1){for(var i=1;i=0.10.0" + +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +node-fetch@^1.0.1: + version "1.6.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +promise@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" + dependencies: + asap "~2.0.3" + +react-create-class@15.5.0-alpha.7: + version "15.5.0-alpha.7" + resolved "https://registry.yarnpkg.com/react-create-class/-/react-create-class-15.5.0-alpha.7.tgz#a2bf8846ab0f0e86799e8a2c2346d7a87941a350" + dependencies: + fbjs "^0.8.9" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +ua-parser-js@^0.7.9: + version "0.7.12" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/package.json b/package.json index c4ea09e12a68..6762a7936665 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "merge-stream": "^1.0.0", "object-assign": "^4.1.1", "platform": "^1.1.0", - "react-create-class": "15.5.0-alpha.2", + "react-create-class": "15.5.0-alpha.7", "run-sequence": "^1.1.4", "through2": "^2.0.0", "tmp": "~0.0.28", diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index 7bc995f5506d..de9c9f5cfee5 100644 --- a/src/isomorphic/classic/class/ReactClass.js +++ b/src/isomorphic/classic/class/ReactClass.js @@ -13,32 +13,891 @@ var ReactComponent = require('ReactComponent'); var ReactElement = require('ReactElement'); +var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); +var emptyObject = require('emptyObject'); +var invariant = require('invariant'); var warning = require('warning'); -var factory = require('react-create-class/factory'); +import type { ReactPropTypeLocations } from 'ReactPropTypeLocations'; -var createClass = factory( - ReactComponent, - ReactElement.isValidElement, - ReactNoopUpdateQueue +var MIXINS_KEY = 'mixins'; + +// Helper function to allow the creation of anonymous functions which do not +// have .name set to the name of the variable being assigned to. +function identity(fn) { + return fn; +} + +/** + * Policies that describe methods in `ReactClassInterface`. + */ +type SpecPolicy = + /** + * These methods may be defined only once by the class specification or mixin. + */ + 'DEFINE_ONCE' | + /** + * These methods may be defined by both the class specification and mixins. + * Subsequent definitions will be chained. These methods must return void. + */ + 'DEFINE_MANY' | + /** + * These methods are overriding the base class. + */ + 'OVERRIDE_BASE' | + /** + * These methods are similar to DEFINE_MANY, except we assume they return + * objects. We try to merge the keys of the return values of all the mixed in + * functions. If there is a key conflict we throw. + */ + 'DEFINE_MANY_MERGED'; + + +var injectedMixins = []; + +/** + * Composite components are higher-level components that compose other composite + * or host components. + * + * To create a new type of `ReactClass`, pass a specification of + * your new class to `React.createClass`. The only requirement of your class + * specification is that you implement a `render` method. + * + * var MyComponent = React.createClass({ + * render: function() { + * return
Hello World
; + * } + * }); + * + * The class specification supports a specific protocol of methods that have + * special meaning (e.g. `render`). See `ReactClassInterface` for + * more the comprehensive protocol. Any other properties and methods in the + * class specification will be available on the prototype. + * + * @interface ReactClassInterface + * @internal + */ +var ReactClassInterface: {[key: string]: SpecPolicy} = { + + /** + * An array of Mixin objects to include when defining your component. + * + * @type {array} + * @optional + */ + mixins: 'DEFINE_MANY', + + /** + * An object containing properties and methods that should be defined on + * the component's constructor instead of its prototype (static methods). + * + * @type {object} + * @optional + */ + statics: 'DEFINE_MANY', + + /** + * Definition of prop types for this component. + * + * @type {object} + * @optional + */ + propTypes: 'DEFINE_MANY', + + /** + * Definition of context types for this component. + * + * @type {object} + * @optional + */ + contextTypes: 'DEFINE_MANY', + + /** + * Definition of context types this component sets for its children. + * + * @type {object} + * @optional + */ + childContextTypes: 'DEFINE_MANY', + + // ==== Definition methods ==== + + /** + * Invoked when the component is mounted. Values in the mapping will be set on + * `this.props` if that prop is not specified (i.e. using an `in` check). + * + * This method is invoked before `getInitialState` and therefore cannot rely + * on `this.state` or use `this.setState`. + * + * @return {object} + * @optional + */ + getDefaultProps: 'DEFINE_MANY_MERGED', + + /** + * Invoked once before the component is mounted. The return value will be used + * as the initial value of `this.state`. + * + * getInitialState: function() { + * return { + * isOn: false, + * fooBaz: new BazFoo() + * } + * } + * + * @return {object} + * @optional + */ + getInitialState: 'DEFINE_MANY_MERGED', + + /** + * @return {object} + * @optional + */ + getChildContext: 'DEFINE_MANY_MERGED', + + /** + * Uses props from `this.props` and state from `this.state` to render the + * structure of the component. + * + * No guarantees are made about when or how often this method is invoked, so + * it must not have side effects. + * + * render: function() { + * var name = this.props.name; + * return
Hello, {name}!
; + * } + * + * @return {ReactComponent} + * @nosideeffects + * @required + */ + render: 'DEFINE_ONCE', + + + + // ==== Delegate methods ==== + + /** + * Invoked when the component is initially created and about to be mounted. + * This may have side effects, but any external subscriptions or data created + * by this method must be cleaned up in `componentWillUnmount`. + * + * @optional + */ + componentWillMount: 'DEFINE_MANY', + + /** + * Invoked when the component has been mounted and has a DOM representation. + * However, there is no guarantee that the DOM node is in the document. + * + * Use this as an opportunity to operate on the DOM when the component has + * been mounted (initialized and rendered) for the first time. + * + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidMount: 'DEFINE_MANY', + + /** + * Invoked before the component receives new props. + * + * Use this as an opportunity to react to a prop transition by updating the + * state using `this.setState`. Current props are accessed via `this.props`. + * + * componentWillReceiveProps: function(nextProps, nextContext) { + * this.setState({ + * likesIncreasing: nextProps.likeCount > this.props.likeCount + * }); + * } + * + * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop + * transition may cause a state change, but the opposite is not true. If you + * need it, you are probably looking for `componentWillUpdate`. + * + * @param {object} nextProps + * @optional + */ + componentWillReceiveProps: 'DEFINE_MANY', + + /** + * Invoked while deciding if the component should be updated as a result of + * receiving new props, state and/or context. + * + * Use this as an opportunity to `return false` when you're certain that the + * transition to the new props/state/context will not require a component + * update. + * + * shouldComponentUpdate: function(nextProps, nextState, nextContext) { + * return !equal(nextProps, this.props) || + * !equal(nextState, this.state) || + * !equal(nextContext, this.context); + * } + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @return {boolean} True if the component should update. + * @optional + */ + shouldComponentUpdate: 'DEFINE_ONCE', + + /** + * Invoked when the component is about to update due to a transition from + * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` + * and `nextContext`. + * + * Use this as an opportunity to perform preparation before an update occurs. + * + * NOTE: You **cannot** use `this.setState()` in this method. + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @param {ReactReconcileTransaction} transaction + * @optional + */ + componentWillUpdate: 'DEFINE_MANY', + + /** + * Invoked when the component's DOM representation has been updated. + * + * Use this as an opportunity to operate on the DOM when the component has + * been updated. + * + * @param {object} prevProps + * @param {?object} prevState + * @param {?object} prevContext + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidUpdate: 'DEFINE_MANY', + + /** + * Invoked when the component is about to be removed from its parent and have + * its DOM representation destroyed. + * + * Use this as an opportunity to deallocate any external resources. + * + * NOTE: There is no `componentDidUnmount` since your component will have been + * destroyed by that point. + * + * @optional + */ + componentWillUnmount: 'DEFINE_MANY', + + + + // ==== Advanced methods ==== + + /** + * Updates the component's currently mounted DOM representation. + * + * By default, this implements React's rendering and reconciliation algorithm. + * Sophisticated clients may wish to override this. + * + * @param {ReactReconcileTransaction} transaction + * @internal + * @overridable + */ + updateComponent: 'OVERRIDE_BASE', + +}; + +/** + * Mapping from class specification keys to special processing functions. + * + * Although these are declared like instance properties in the specification + * when defining classes using `React.createClass`, they are actually static + * and are accessible on the constructor instead of the prototype. Despite + * being static, they must be defined outside of the "statics" key under + * which all other static methods are defined. + */ +var RESERVED_SPEC_KEYS = { + displayName: function(Constructor, displayName) { + Constructor.displayName = displayName; + }, + mixins: function(Constructor, mixins) { + if (mixins) { + for (var i = 0; i < mixins.length; i++) { + mixSpecIntoComponent(Constructor, mixins[i]); + } + } + }, + childContextTypes: function(Constructor, childContextTypes) { + if (__DEV__) { + validateTypeDef( + Constructor, + childContextTypes, + 'childContext' + ); + } + Constructor.childContextTypes = Object.assign( + {}, + Constructor.childContextTypes, + childContextTypes + ); + }, + contextTypes: function(Constructor, contextTypes) { + if (__DEV__) { + validateTypeDef( + Constructor, + contextTypes, + 'context' + ); + } + Constructor.contextTypes = Object.assign( + {}, + Constructor.contextTypes, + contextTypes + ); + }, + /** + * Special case getDefaultProps which should move into statics but requires + * automatic merging. + */ + getDefaultProps: function(Constructor, getDefaultProps) { + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps = createMergedResultFunction( + Constructor.getDefaultProps, + getDefaultProps + ); + } else { + Constructor.getDefaultProps = getDefaultProps; + } + }, + propTypes: function(Constructor, propTypes) { + if (__DEV__) { + validateTypeDef( + Constructor, + propTypes, + 'prop' + ); + } + Constructor.propTypes = Object.assign( + {}, + Constructor.propTypes, + propTypes + ); + }, + statics: function(Constructor, statics) { + mixStaticSpecIntoComponent(Constructor, statics); + }, + autobind: function() {}, // noop +}; + +function validateTypeDef( + Constructor, + typeDef, + location: ReactPropTypeLocations, +) { + for (var propName in typeDef) { + if (typeDef.hasOwnProperty(propName)) { + // use a warning instead of an invariant so components + // don't show up in prod but only in __DEV__ + warning( + typeof typeDef[propName] === 'function', + '%s: %s type `%s` is invalid; it must be a function, usually from ' + + 'React.PropTypes.', + Constructor.displayName || 'ReactClass', + ReactPropTypeLocationNames[location], + propName + ); + } + } +} + +function validateMethodOverride(isAlreadyDefined, name) { + var specPolicy = ReactClassInterface.hasOwnProperty(name) ? + ReactClassInterface[name] : + null; + + // Disallow overriding of base class methods unless explicitly allowed. + if (ReactClassMixin.hasOwnProperty(name)) { + invariant( + specPolicy === 'OVERRIDE_BASE', + 'ReactClassInterface: You are attempting to override ' + + '`%s` from your class specification. Ensure that your method names ' + + 'do not overlap with React methods.', + name + ); + } + + // Disallow defining methods more than once unless explicitly allowed. + if (isAlreadyDefined) { + invariant( + specPolicy === 'DEFINE_MANY' || + specPolicy === 'DEFINE_MANY_MERGED', + 'ReactClassInterface: You are attempting to define ' + + '`%s` on your component more than once. This conflict may be due ' + + 'to a mixin.', + name + ); + } +} + +/** + * Mixin helper which handles policy validation and reserved + * specification keys when building React classes. + */ +function mixSpecIntoComponent(Constructor, spec) { + if (!spec) { + if (__DEV__) { + var typeofSpec = typeof spec; + var isMixinValid = typeofSpec === 'object' && spec !== null; + + warning( + isMixinValid, + '%s: You\'re attempting to include a mixin that is either null ' + + 'or not an object. Check the mixins included by the component, ' + + 'as well as any mixins they include themselves. ' + + 'Expected object but got %s.', + Constructor.displayName || 'ReactClass', + spec === null ? null : typeofSpec + ); + } + + return; + } + + invariant( + typeof spec !== 'function', + 'ReactClass: You\'re attempting to ' + + 'use a component class or function as a mixin. Instead, just use a ' + + 'regular object.' + ); + invariant( + !ReactElement.isValidElement(spec), + 'ReactClass: You\'re attempting to ' + + 'use a component as a mixin. Instead, just use a regular object.' + ); + + var proto = Constructor.prototype; + var autoBindPairs = proto.__reactAutoBindPairs; + + // 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(Constructor, spec.mixins); + } + + for (var name in spec) { + if (!spec.hasOwnProperty(name)) { + continue; + } + + if (name === MIXINS_KEY) { + // We have already handled mixins in a special case above. + continue; + } + + var property = spec[name]; + var isAlreadyDefined = proto.hasOwnProperty(name); + validateMethodOverride(isAlreadyDefined, name); + + if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { + RESERVED_SPEC_KEYS[name](Constructor, property); + } else { + // Setup methods on prototype: + // The following member methods should not be automatically bound: + // 1. Expected ReactClass methods (in the "interface"). + // 2. Overridden methods (that were mixed in). + var isReactClassMethod = + ReactClassInterface.hasOwnProperty(name); + var isFunction = typeof property === 'function'; + var shouldAutoBind = + isFunction && + !isReactClassMethod && + !isAlreadyDefined && + spec.autobind !== false; + + if (shouldAutoBind) { + autoBindPairs.push(name, property); + proto[name] = property; + } else { + if (isAlreadyDefined) { + var specPolicy = ReactClassInterface[name]; + + // These cases should already be caught by validateMethodOverride. + invariant( + isReactClassMethod && ( + specPolicy === 'DEFINE_MANY_MERGED' || + specPolicy === 'DEFINE_MANY' + ), + 'ReactClass: Unexpected spec policy %s for key %s ' + + 'when mixing in component specs.', + specPolicy, + name + ); + + // For methods which are defined more than once, call the existing + // methods before calling the new property, merging if appropriate. + if (specPolicy === 'DEFINE_MANY_MERGED') { + proto[name] = createMergedResultFunction(proto[name], property); + } else if (specPolicy === 'DEFINE_MANY') { + proto[name] = createChainedFunction(proto[name], property); + } + } else { + proto[name] = property; + if (__DEV__) { + // Add verbose displayName to the function, which helps when looking + // at profiling tools. + if (typeof property === 'function' && spec.displayName) { + proto[name].displayName = spec.displayName + '_' + name; + } + } + } + } + } + } +} + +function mixStaticSpecIntoComponent(Constructor, statics) { + if (!statics) { + return; + } + for (var name in statics) { + var property = statics[name]; + if (!statics.hasOwnProperty(name)) { + continue; + } + + var isReserved = name in RESERVED_SPEC_KEYS; + invariant( + !isReserved, + 'ReactClass: You are attempting to define a reserved ' + + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + + 'as an instance property instead; it will still be accessible on the ' + + 'constructor.', + 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 + ); + Constructor[name] = property; + } +} + +/** + * Merge two objects, but throw if both contain the same key. + * + * @param {object} one The first object, which is mutated. + * @param {object} two The second object + * @return {object} one after it has been mutated to contain everything in two. + */ +function mergeIntoWithNoDuplicateKeys(one, two) { + invariant( + one && two && typeof one === 'object' && typeof two === 'object', + 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.' + ); + + for (var key in two) { + if (two.hasOwnProperty(key)) { + invariant( + one[key] === undefined, + 'mergeIntoWithNoDuplicateKeys(): ' + + 'Tried to merge two objects with the same key: `%s`. This conflict ' + + 'may be due to a mixin; in particular, this may be caused by two ' + + 'getInitialState() or getDefaultProps() methods returning objects ' + + 'with clashing keys.', + key + ); + one[key] = two[key]; + } + } + return one; +} + +/** + * Creates a function that invokes two functions and merges their return values. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createMergedResultFunction(one, two) { + return function mergedResult() { + var a = one.apply(this, arguments); + var b = two.apply(this, arguments); + if (a == null) { + return b; + } else if (b == null) { + return a; + } + var c = {}; + mergeIntoWithNoDuplicateKeys(c, a); + mergeIntoWithNoDuplicateKeys(c, b); + return c; + }; +} + +/** + * Creates a function that invokes two functions and ignores their return vales. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createChainedFunction(one, two) { + return function chainedFunction() { + one.apply(this, arguments); + two.apply(this, arguments); + }; +} + +/** + * Binds a method to the component. + * + * @param {object} component Component whose method is going to be bound. + * @param {function} method Method to be bound. + * @return {function} The bound method. + */ +function bindAutoBindMethod(component, method) { + var boundMethod = method.bind(component); + if (__DEV__) { + boundMethod.__reactBoundContext = component; + boundMethod.__reactBoundMethod = method; + boundMethod.__reactBoundArguments = null; + var componentName = component.constructor.displayName; + var _bind = boundMethod.bind; + boundMethod.bind = function(newThis, ...args) { + // User is trying to bind() an autobound method; we effectively will + // ignore the value of "this" that the user is trying to use, so + // let's warn. + if (newThis !== component && newThis !== null) { + warning( + false, + 'bind(): React component methods may only be bound to the ' + + 'component instance. See %s', + componentName + ); + } else if (!args.length) { + warning( + false, + 'bind(): You are binding a component method to the component. ' + + 'React does this for you automatically in a high-performance ' + + 'way, so you can safely remove this call. See %s', + componentName + ); + return boundMethod; + } + var reboundMethod = _bind.apply(boundMethod, arguments); + reboundMethod.__reactBoundContext = component; + reboundMethod.__reactBoundMethod = method; + reboundMethod.__reactBoundArguments = args; + return reboundMethod; + }; + } + return boundMethod; +} + +/** + * Binds all auto-bound methods in a component. + * + * @param {object} component Component whose method is going to be bound. + */ +function bindAutoBindMethods(component) { + var pairs = component.__reactAutoBindPairs; + for (var i = 0; i < pairs.length; i += 2) { + var autoBindKey = pairs[i]; + var method = pairs[i + 1]; + component[autoBindKey] = bindAutoBindMethod( + component, + method + ); + } +} + +/** + * Add more to the ReactClass base class. These are all legacy features and + * therefore not already part of the modern ReactComponent. + */ +var ReactClassMixin = { + + /** + * TODO: This will be deprecated because state should always keep a consistent + * type signature and the only use case for this, is to avoid that. + */ + replaceState: function(newState, callback) { + this.updater.enqueueReplaceState(this, newState); + if (callback) { + this.updater.enqueueCallback(this, callback, 'replaceState'); + } + }, + + /** + * Checks whether or not this composite component is mounted. + * @return {boolean} True if mounted, false otherwise. + * @protected + * @final + */ + isMounted: function() { + return this.updater.isMounted(this); + }, +}; + +var ReactClassComponent = function() {}; +Object.assign( + ReactClassComponent.prototype, + ReactComponent.prototype, + ReactClassMixin ); let didWarnDeprecated = false; -module.exports = { - createClass(...args) { +/** + * Module for creating composite components. + * + * @class ReactClass + */ +var ReactClass = { + + /** + * Creates a composite component class given a class specification. + * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass + * + * @param {object} spec Class specification (which must define `render`). + * @return {function} Component constructor function. + * @public + */ + createClass: function(spec) { if (__DEV__) { warning( didWarnDeprecated, - 'React.createClass is deprecated and will be removed in version 16. ' + + '%s: React.createClass is deprecated and will be removed in version 16. ' + 'Use plain JavaScript classes instead. If you\'re not yet ready to ' + 'migrate, react-create-class is available on npm as a ' + - 'drop-in replacement.' + 'drop-in replacement.', + (spec && spec.displayName) || 'A Component', ); didWarnDeprecated = true; } - return createClass(...args); + + // To keep our warnings more understandable, we'll use a little hack here to + // ensure that Constructor.name !== 'Constructor'. This makes sure we don't + // unnecessarily identify a class without displayName as 'Constructor'. + var Constructor = identity(function(props, context, updater) { + // This constructor gets overridden by mocks. The argument is used + // by mocks to assert on what gets mounted. + + if (__DEV__) { + warning( + this instanceof Constructor, + 'Something is calling a React component directly. Use a factory or ' + + 'JSX instead. See: https://fb.me/react-legacyfactory' + ); + } + + // Wire up auto-binding + if (this.__reactAutoBindPairs.length) { + bindAutoBindMethods(this); + } + + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + + this.state = null; + + // ReactClasses doesn't have constructors. Instead, they use the + // getInitialState and componentWillMount methods for initialization. + + var initialState = this.getInitialState ? this.getInitialState() : null; + if (__DEV__) { + // We allow auto-mocks to proceed as if they're returning null. + if (initialState === undefined && + this.getInitialState._isMockFunction) { + // This is probably bad practice. Consider warning here and + // deprecating this convenience. + initialState = null; + } + } + invariant( + typeof initialState === 'object' && !Array.isArray(initialState), + '%s.getInitialState(): must return an object or null', + Constructor.displayName || 'ReactCompositeComponent' + ); + + this.state = initialState; + }); + Constructor.prototype = new ReactClassComponent(); + Constructor.prototype.constructor = Constructor; + Constructor.prototype.__reactAutoBindPairs = []; + + injectedMixins.forEach( + mixSpecIntoComponent.bind(null, Constructor) + ); + + mixSpecIntoComponent(Constructor, spec); + + // Initialize the defaultProps property after all mixins have been merged. + if (Constructor.getDefaultProps) { + Constructor.defaultProps = Constructor.getDefaultProps(); + } + + if (__DEV__) { + // This is a tag to indicate that the use of these method names is ok, + // since it's used with createClass. If it's not, then it's likely a + // mistake so we'll warn you to use the static property, property + // initializer or constructor respectively. + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps.isReactClassApproved = {}; + } + if (Constructor.prototype.getInitialState) { + Constructor.prototype.getInitialState.isReactClassApproved = {}; + } + } + + invariant( + Constructor.prototype.render, + 'createClass(...): Class specification must implement a `render` method.' + ); + + if (__DEV__) { + warning( + !Constructor.prototype.componentShouldUpdate, + '%s has a method called ' + + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + + 'The name is phrased as a question because the function is ' + + 'expected to return a value.', + spec.displayName || 'A component' + ); + warning( + !Constructor.prototype.componentWillRecieveProps, + '%s has a method called ' + + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', + spec.displayName || 'A component' + ); + } + + // Reduce time spent doing lookups by setting these on the prototype. + for (var methodName in ReactClassInterface) { + if (!Constructor.prototype[methodName]) { + Constructor.prototype[methodName] = null; + } + } + + return Constructor; + }, + + injection: { + injectMixin: function(mixin) { + injectedMixins.push(mixin); + }, }, + }; + +module.exports = ReactClass; diff --git a/src/isomorphic/classic/class/__tests__/ReactClass-test.js b/src/isomorphic/classic/class/__tests__/ReactClass-test.js index e992af7429b4..b5f6f539c084 100644 --- a/src/isomorphic/classic/class/__tests__/ReactClass-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactClass-test.js @@ -26,6 +26,7 @@ describe('ReactClass-spec', () => { it('should warn on first call to React.createClass', () => { spyOn(console, 'error'); const spec = { + displayName: 'MyComponent', render() { return
; }, @@ -35,7 +36,7 @@ describe('ReactClass-spec', () => { expect(console.error.calls.count()).toEqual(1); expect(console.error.calls.count()).toEqual(1); expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: React.createClass is deprecated and will be removed in ' + + 'Warning: MyComponent: React.createClass is deprecated and will be removed in ' + 'version 16. Use plain JavaScript classes instead. If you\'re not yet ' + 'ready to migrate, react-create-class is available on npm as a ' + 'drop-in replacement.' diff --git a/src/isomorphic/classic/class/__tests__/ReactCreateClass-test.js b/src/isomorphic/classic/class/__tests__/ReactCreateClass-test.js new file mode 100644 index 000000000000..7e992f8a24a4 --- /dev/null +++ b/src/isomorphic/classic/class/__tests__/ReactCreateClass-test.js @@ -0,0 +1,444 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactDOM; +var ReactTestUtils; +var ReactCreateClass; + +describe('ReactClass-spec', () => { + + beforeEach(() => { + React = require('React'); + ReactDOM = require('ReactDOM'); + ReactTestUtils = require('ReactTestUtils'); + var ReactCreateClassFactory = require('react-create-class/factory'); + ReactCreateClass = ReactCreateClassFactory( + React.Component, + React.isValidElement, + require('ReactNoopUpdateQueue'), + ); + }); + + it('should throw when `render` is not specified', () => { + expect(function() { + ReactCreateClass({}); + }).toThrowError( + 'createClass(...): Class specification must implement a `render` method.' + ); + }); + + // TODO: Update babel-plugin-transform-react-display-name + xit('should copy `displayName` onto the Constructor', () => { + var TestComponent = ReactCreateClass({ + render: function() { + return
; + }, + }); + + expect(TestComponent.displayName) + .toBe('TestComponent'); + }); + + it('should copy prop types onto the Constructor', () => { + var propValidator = jest.fn(); + var TestComponent = ReactCreateClass({ + propTypes: { + value: propValidator, + }, + render: function() { + return
; + }, + }); + + expect(TestComponent.propTypes).toBeDefined(); + expect(TestComponent.propTypes.value) + .toBe(propValidator); + }); + + it('should warn on invalid prop types', () => { + spyOn(console, 'error'); + ReactCreateClass({ + displayName: 'Component', + propTypes: { + prop: null, + }, + render: function() { + return {this.props.prop}; + }, + }); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component: prop type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should warn on invalid context types', () => { + spyOn(console, 'error'); + ReactCreateClass({ + displayName: 'Component', + contextTypes: { + prop: null, + }, + render: function() { + return {this.props.prop}; + }, + }); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component: context type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should throw on invalid child context types', () => { + spyOn(console, 'error'); + ReactCreateClass({ + displayName: 'Component', + childContextTypes: { + prop: null, + }, + render: function() { + return {this.props.prop}; + }, + }); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Component: child context type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should warn when mispelling shouldComponentUpdate', () => { + spyOn(console, 'error'); + + ReactCreateClass({ + componentShouldUpdate: function() { + return false; + }, + render: function() { + return
; + }, + }); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: A component has a method called componentShouldUpdate(). Did you ' + + 'mean shouldComponentUpdate()? The name is phrased as a question ' + + 'because the function is expected to return a value.' + ); + + ReactCreateClass({ + displayName: 'NamedComponent', + componentShouldUpdate: function() { + return false; + }, + render: function() { + return
; + }, + }); + expect(console.error.calls.count()).toBe(2); + expect(console.error.calls.argsFor(1)[0]).toBe( + 'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' + + 'mean shouldComponentUpdate()? The name is phrased as a question ' + + 'because the function is expected to return a value.' + ); + }); + + it('should warn when mispelling componentWillReceiveProps', () => { + spyOn(console, 'error'); + ReactCreateClass({ + componentWillRecieveProps: function() { + return false; + }, + render: function() { + return
; + }, + }); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: A component has a method called componentWillRecieveProps(). Did you ' + + 'mean componentWillReceiveProps()?' + ); + }); + + it('should throw if a reserved property is in statics', () => { + expect(function() { + ReactCreateClass({ + statics: { + getDefaultProps: function() { + return { + foo: 0, + }; + }, + }, + + render: function() { + return ; + }, + }); + }).toThrowError( + 'ReactClass: You are attempting to define a reserved property, ' + + '`getDefaultProps`, that shouldn\'t be on the "statics" key. Define ' + + 'it as an instance property instead; it will still be accessible on ' + + 'the constructor.' + ); + }); + + // TODO: Consider actually moving these to statics or drop this unit test. + + xit('should warn when using deprecated non-static spec keys', () => { + spyOn(console, 'error'); + ReactCreateClass({ + mixins: [{}], + propTypes: { + foo: React.PropTypes.string, + }, + contextTypes: { + foo: React.PropTypes.string, + }, + childContextTypes: { + foo: React.PropTypes.string, + }, + render: function() { + return
; + }, + }); + expect(console.error.calls.count()).toBe(4); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'createClass(...): `mixins` is now a static property and should ' + + 'be defined inside "statics".' + ); + expect(console.error.calls.argsFor(1)[0]).toBe( + 'createClass(...): `propTypes` is now a static property and should ' + + 'be defined inside "statics".' + ); + expect(console.error.calls.argsFor(2)[0]).toBe( + 'createClass(...): `contextTypes` is now a static property and ' + + 'should be defined inside "statics".' + ); + expect(console.error.calls.argsFor(3)[0]).toBe( + 'createClass(...): `childContextTypes` is now a static property and ' + + 'should be defined inside "statics".' + ); + }); + + it('should support statics', () => { + var Component = ReactCreateClass({ + statics: { + abc: 'def', + def: 0, + ghi: null, + jkl: 'mno', + pqr: function() { + return this; + }, + }, + + render: function() { + return ; + }, + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.constructor.abc).toBe('def'); + expect(Component.abc).toBe('def'); + expect(instance.constructor.def).toBe(0); + expect(Component.def).toBe(0); + expect(instance.constructor.ghi).toBe(null); + expect(Component.ghi).toBe(null); + expect(instance.constructor.jkl).toBe('mno'); + expect(Component.jkl).toBe('mno'); + expect(instance.constructor.pqr()).toBe(Component); + expect(Component.pqr()).toBe(Component); + }); + + it('should work with object getInitialState() return values', () => { + var Component = ReactCreateClass({ + getInitialState: function() { + return { + occupation: 'clown', + }; + }, + render: function() { + return ; + }, + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state.occupation).toEqual('clown'); + }); + + it('renders based on context getInitialState', () => { + var Foo = ReactCreateClass({ + contextTypes: { + className: React.PropTypes.string, + }, + getInitialState() { + return {className: this.context.className}; + }, + render() { + return ; + }, + }); + + var Outer = ReactCreateClass({ + childContextTypes: { + className: React.PropTypes.string, + }, + getChildContext() { + return {className: 'foo'}; + }, + render() { + return ; + }, + }); + + var container = document.createElement('div'); + ReactDOM.render(, container); + expect(container.firstChild.className).toBe('foo'); + }); + + it('should throw with non-object getInitialState() return values', () => { + [['an array'], 'a string', 1234].forEach(function(state) { + var Component = ReactCreateClass({ + getInitialState: function() { + return state; + }, + render: function() { + return ; + }, + }); + var instance = ; + expect(function() { + instance = ReactTestUtils.renderIntoDocument(instance); + }).toThrowError( + 'Component.getInitialState(): must return an object or null' + ); + }); + }); + + it('should work with a null getInitialState() return value', () => { + var Component = ReactCreateClass({ + getInitialState: function() { + return null; + }, + render: function() { + return ; + }, + }); + expect( + () => ReactTestUtils.renderIntoDocument() + ).not.toThrow(); + }); + + it('should throw when using legacy factories', () => { + spyOn(console, 'error'); + var Component = ReactCreateClass({ + render() { + return
; + }, + }); + + expect(() => Component()).toThrow(); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: Something is calling a React component directly. Use a ' + + 'factory or JSX instead. See: https://fb.me/react-legacyfactory' + ); + }); + + it('replaceState and callback works', () => { + var ops = []; + var Component = ReactCreateClass({ + getInitialState() { + return { step: 0 }; + }, + render() { + ops.push('Render: ' + this.state.step); + return
; + } + }); + + var instance = ReactTestUtils.renderIntoDocument(); + instance.replaceState({ step: 1 }, () => { + ops.push('Callback: ' + instance.state.step); + }); + expect(ops).toEqual([ + 'Render: 0', + 'Render: 1', + 'Callback: 1', + ]); + }); + + it('isMounted works', () => { + spyOn(console, 'error'); + + var ops = []; + var instance; + var Component = ReactCreateClass({ + displayName: 'MyComponent', + log(name) { + ops.push(`${name}: ${this.isMounted()}`); + }, + getInitialState() { + this.log('getInitialState'); + return {}; + }, + componentWillMount() { + this.log('componentWillMount'); + }, + componentDidMount() { + this.log('componentDidMount'); + }, + componentWillUpdate() { + this.log('componentWillUpdate'); + }, + componentDidUpdate() { + this.log('componentDidUpdate'); + }, + componentWillUnmount() { + this.log('componentWillUnmount'); + }, + render() { + instance = this; + this.log('render'); + return
; + } + }); + + var container = document.createElement('div'); + ReactDOM.render(, container); + ReactDOM.render(, container); + ReactDOM.unmountComponentAtNode(container); + instance.log('after unmount'); + expect(ops).toEqual([ + 'getInitialState: false', + 'componentWillMount: false', + 'render: false', + 'componentDidMount: true', + 'componentWillUpdate: true', + 'render: true', + 'componentDidUpdate: true', + 'componentWillUnmount: false', + 'after unmount: false', + ]); + + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' + + 'clean up subscriptions and pending requests in componentWillUnmount ' + + 'to prevent memory leaks.' + ); + }); +}); diff --git a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js index 1b555520a783..b72cb7095e3c 100644 --- a/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js @@ -194,7 +194,7 @@ var ReactUpdateQueue = { * @param {object} completeState Next state. * @internal */ - enqueueReplaceState: function(publicInstance, completeState) { + enqueueReplaceState: function(publicInstance, completeState, callback) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'replaceState' @@ -207,6 +207,16 @@ var ReactUpdateQueue = { internalInstance._pendingStateQueue = [completeState]; internalInstance._pendingReplaceState = true; + // Future-proof 15.5 + if (callback !== undefined && callback !== null) { + ReactUpdateQueue.validateCallback(callback, 'replaceState'); + if (internalInstance._pendingCallbacks) { + internalInstance._pendingCallbacks.push(callback); + } else { + internalInstance._pendingCallbacks = [callback]; + } + } + enqueueUpdate(internalInstance); },