UNPKG

react-addons

Version:

Simple packaging of react addons to avoid fiddly 'react/addons' npm module.

1,456 lines (1,330 loc) 48.9 kB
/** * Copyright 2013-2014 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @providesModule ReactCompositeComponent */ "use strict"; var ReactComponent = require("./ReactComponent"); var ReactContext = require("./ReactContext"); var ReactCurrentOwner = require("./ReactCurrentOwner"); var ReactErrorUtils = require("./ReactErrorUtils"); var ReactOwner = require("./ReactOwner"); var ReactPerf = require("./ReactPerf"); var ReactPropTransferer = require("./ReactPropTransferer"); var ReactPropTypeLocations = require("./ReactPropTypeLocations"); var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames"); var ReactUpdates = require("./ReactUpdates"); var invariant = require("./invariant"); var keyMirror = require("./keyMirror"); var merge = require("./merge"); var mixInto = require("./mixInto"); var objMap = require("./objMap"); var shouldUpdateReactComponent = require("./shouldUpdateReactComponent"); /** * Policies that describe methods in `ReactCompositeComponentInterface`. */ var SpecPolicy = keyMirror({ /** * These methods may be defined only once by the class specification or mixin. */ DEFINE_ONCE: null, /** * These methods may be defined by both the class specification and mixins. * Subsequent definitions will be chained. These methods must return void. */ DEFINE_MANY: null, /** * These methods are overriding the base ReactCompositeComponent class. */ OVERRIDE_BASE: null, /** * 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: null }); /** * Composite components are higher-level components that compose other composite * or native components. * * To create a new type of `ReactCompositeComponent`, 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 <div>Hello World</div>; * } * }); * * The class specification supports a specific protocol of methods that have * special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for * more the comprehensive protocol. Any other properties and methods in the * class specification will available on the prototype. * * @interface ReactCompositeComponentInterface * @internal */ var ReactCompositeComponentInterface = { /** * An array of Mixin objects to include when defining your component. * * @type {array} * @optional */ mixins: SpecPolicy.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: SpecPolicy.DEFINE_MANY, /** * Definition of prop types for this component. * * @type {object} * @optional */ propTypes: SpecPolicy.DEFINE_MANY, /** * Definition of context types for this component. * * @type {object} * @optional */ contextTypes: SpecPolicy.DEFINE_MANY, /** * Definition of context types this component sets for its children. * * @type {object} * @optional */ childContextTypes: SpecPolicy.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: SpecPolicy.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: SpecPolicy.DEFINE_MANY_MERGED, /** * @return {object} * @optional */ getChildContext: SpecPolicy.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 <div>Hello, {name}!</div>; * } * * @return {ReactComponent} * @nosideeffects * @required */ render: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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: SpecPolicy.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(ConvenienceConstructor, displayName) { ConvenienceConstructor.componentConstructor.displayName = displayName; }, mixins: function(ConvenienceConstructor, mixins) { if (mixins) { for (var i = 0; i < mixins.length; i++) { mixSpecIntoComponent(ConvenienceConstructor, mixins[i]); } } }, childContextTypes: function(ConvenienceConstructor, childContextTypes) { var Constructor = ConvenienceConstructor.componentConstructor; validateTypeDef( Constructor, childContextTypes, ReactPropTypeLocations.childContext ); Constructor.childContextTypes = merge( Constructor.childContextTypes, childContextTypes ); }, contextTypes: function(ConvenienceConstructor, contextTypes) { var Constructor = ConvenienceConstructor.componentConstructor; validateTypeDef( Constructor, contextTypes, ReactPropTypeLocations.context ); Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes); }, propTypes: function(ConvenienceConstructor, propTypes) { var Constructor = ConvenienceConstructor.componentConstructor; validateTypeDef( Constructor, propTypes, ReactPropTypeLocations.prop ); Constructor.propTypes = merge(Constructor.propTypes, propTypes); }, statics: function(ConvenienceConstructor, statics) { mixStaticSpecIntoComponent(ConvenienceConstructor, statics); } }; function validateTypeDef(Constructor, typeDef, location) { for (var propName in typeDef) { if (typeDef.hasOwnProperty(propName)) { ("production" !== process.env.NODE_ENV ? invariant( typeof typeDef[propName] == 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'React.PropTypes.', Constructor.displayName || 'ReactCompositeComponent', ReactPropTypeLocationNames[location], propName ) : invariant(typeof typeDef[propName] == 'function')); } } } function validateMethodOverride(proto, name) { var specPolicy = ReactCompositeComponentInterface[name]; // Disallow overriding of base class methods unless explicitly allowed. if (ReactCompositeComponentMixin.hasOwnProperty(name)) { ("production" !== process.env.NODE_ENV ? invariant( specPolicy === SpecPolicy.OVERRIDE_BASE, 'ReactCompositeComponentInterface: You are attempting to override ' + '`%s` from your class specification. Ensure that your method names ' + 'do not overlap with React methods.', name ) : invariant(specPolicy === SpecPolicy.OVERRIDE_BASE)); } // Disallow defining methods more than once unless explicitly allowed. if (proto.hasOwnProperty(name)) { ("production" !== process.env.NODE_ENV ? invariant( specPolicy === SpecPolicy.DEFINE_MANY || specPolicy === SpecPolicy.DEFINE_MANY_MERGED, 'ReactCompositeComponentInterface: You are attempting to define ' + '`%s` on your component more than once. This conflict may be due ' + 'to a mixin.', name ) : invariant(specPolicy === SpecPolicy.DEFINE_MANY || specPolicy === SpecPolicy.DEFINE_MANY_MERGED)); } } function validateLifeCycleOnReplaceState(instance) { var compositeLifeCycleState = instance._compositeLifeCycleState; ("production" !== process.env.NODE_ENV ? invariant( instance.isMounted() || compositeLifeCycleState === CompositeLifeCycle.MOUNTING, 'replaceState(...): Can only update a mounted or mounting component.' ) : invariant(instance.isMounted() || compositeLifeCycleState === CompositeLifeCycle.MOUNTING)); ("production" !== process.env.NODE_ENV ? invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE, 'replaceState(...): Cannot update during an existing state transition ' + '(such as within `render`). This could potentially cause an infinite ' + 'loop so it is forbidden.' ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE)); ("production" !== process.env.NODE_ENV ? invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, 'replaceState(...): Cannot update while unmounting component. This ' + 'usually means you called setState() on an unmounted component.' ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING)); } /** * Custom version of `mixInto` which handles policy validation and reserved * specification keys when building `ReactCompositeComponent` classses. */ function mixSpecIntoComponent(ConvenienceConstructor, spec) { ("production" !== process.env.NODE_ENV ? invariant( !isValidClass(spec), 'ReactCompositeComponent: You\'re attempting to ' + 'use a component class as a mixin. Instead, just use a regular object.' ) : invariant(!isValidClass(spec))); ("production" !== process.env.NODE_ENV ? invariant( !ReactComponent.isValidComponent(spec), 'ReactCompositeComponent: You\'re attempting to ' + 'use a component as a mixin. Instead, just use a regular object.' ) : invariant(!ReactComponent.isValidComponent(spec))); var Constructor = ConvenienceConstructor.componentConstructor; var proto = Constructor.prototype; for (var name in spec) { var property = spec[name]; if (!spec.hasOwnProperty(name)) { continue; } validateMethodOverride(proto, name); if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { RESERVED_SPEC_KEYS[name](ConvenienceConstructor, property); } else { // Setup methods on prototype: // The following member methods should not be automatically bound: // 1. Expected ReactCompositeComponent methods (in the "interface"). // 2. Overridden methods (that were mixed in). var isCompositeComponentMethod = name in ReactCompositeComponentInterface; var isInherited = name in proto; var markedDontBind = property && property.__reactDontBind; var isFunction = typeof property === 'function'; var shouldAutoBind = isFunction && !isCompositeComponentMethod && !isInherited && !markedDontBind; if (shouldAutoBind) { if (!proto.__reactAutoBindMap) { proto.__reactAutoBindMap = {}; } proto.__reactAutoBindMap[name] = property; proto[name] = property; } else { if (isInherited) { // For methods which are defined more than once, call the existing // methods before calling the new property. if (ReactCompositeComponentInterface[name] === SpecPolicy.DEFINE_MANY_MERGED) { proto[name] = createMergedResultFunction(proto[name], property); } else { proto[name] = createChainedFunction(proto[name], property); } } else { proto[name] = property; } } } } } function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) { if (!statics) { return; } for (var name in statics) { var property = statics[name]; if (!statics.hasOwnProperty(name) || !property) { return; } var isInherited = name in ConvenienceConstructor; var result = property; if (isInherited) { var existingProperty = ConvenienceConstructor[name]; var existingType = typeof existingProperty; var propertyType = typeof property; ("production" !== process.env.NODE_ENV ? invariant( existingType === 'function' && propertyType === 'function', 'ReactCompositeComponent: You are attempting to define ' + '`%s` on your component more than once, but that is only supported ' + 'for functions, which are chained together. This conflict may be ' + 'due to a mixin.', name ) : invariant(existingType === 'function' && propertyType === 'function')); result = createChainedFunction(existingProperty, property); } ConvenienceConstructor[name] = result; ConvenienceConstructor.componentConstructor[name] = result; } } /** * 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 mergeObjectsWithNoDuplicateKeys(one, two) { ("production" !== process.env.NODE_ENV ? invariant( one && two && typeof one === 'object' && typeof two === 'object', 'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects' ) : invariant(one && two && typeof one === 'object' && typeof two === 'object')); objMap(two, function(value, key) { ("production" !== process.env.NODE_ENV ? invariant( one[key] === undefined, 'mergeObjectsWithNoDuplicateKeys(): ' + 'Tried to merge two objects with the same key: %s', key ) : invariant(one[key] === undefined)); one[key] = value; }); 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; } return mergeObjectsWithNoDuplicateKeys(a, b); }; } /** * 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); }; } if ("production" !== process.env.NODE_ENV) { var unmountedPropertyWhitelist = { constructor: true, construct: true, isOwnedBy: true, // should be deprecated but can have code mod (internal) mountComponent: true, mountComponentIntoNode: true, props: true, type: true, _checkPropTypes: true, _mountComponentIntoNode: true, _processContext: true }; var hasWarnedOnComponentType = {}; var warnIfUnmounted = function(instance, key) { if (instance.__hasBeenMounted) { return; } var name = instance.constructor.displayName || 'Unknown'; var owner = ReactCurrentOwner.current; var ownerName = (owner && owner.constructor.displayName) || 'Unknown'; var warningKey = key + '|' + name + '|' + ownerName; if (hasWarnedOnComponentType.hasOwnProperty(warningKey)) { // We have already warned for this combination. Skip it this time. return; } hasWarnedOnComponentType[warningKey] = true; var context = owner ? ' in ' + ownerName + '.' : ' at the top level.'; var staticMethodExample = '<' + name + ' />.type.' + key + '(...)'; console.warn( 'Invalid access to component property "' + key + '" on ' + name + context + ' See http://fb.me/react-warning-descriptors .' + ' Use a static method instead: ' + staticMethodExample ); }; var defineMembraneProperty = function(membrane, prototype, key) { Object.defineProperty(membrane, key, { configurable: false, enumerable: true, get: function() { if (this !== membrane) { // When this is accessed through a prototype chain we need to check if // this component was mounted. warnIfUnmounted(this, key); } return prototype[key]; }, set: function(value) { if (this !== membrane) { // When this is accessed through a prototype chain, we first check if // this component was mounted. Then we define a value on "this" // instance, effectively disabling the membrane on that prototype // chain. warnIfUnmounted(this, key); Object.defineProperty(this, key, { enumerable: true, configurable: true, writable: true, value: value }); } else { // Otherwise, this should modify the prototype prototype[key] = value; } } }); }; /** * Creates a membrane prototype which wraps the original prototype. If any * property is accessed in an unmounted state, a warning is issued. * * @param {object} prototype Original prototype. * @return {object} The membrane prototype. * @private */ var createMountWarningMembrane = function(prototype) { try { var membrane = Object.create(prototype); for (var key in prototype) { if (unmountedPropertyWhitelist.hasOwnProperty(key)) { continue; } defineMembraneProperty(membrane, prototype, key); } membrane.mountComponent = function() { this.__hasBeenMounted = true; return prototype.mountComponent.apply(this, arguments); }; return membrane; } catch(x) { // In IE8 define property will fail on non-DOM objects. If anything in // the membrane creation fails, we'll bail out and just use the prototype // without warnings. return prototype; } }; } /** * `ReactCompositeComponent` maintains an auxiliary life cycle state in * `this._compositeLifeCycleState` (which can be null). * * This is different from the life cycle state maintained by `ReactComponent` in * `this._lifeCycleState`. The following diagram shows how the states overlap in * time. There are times when the CompositeLifeCycle is null - at those times it * is only meaningful to look at ComponentLifeCycle alone. * * Top Row: ReactComponent.ComponentLifeCycle * Low Row: ReactComponent.CompositeLifeCycle * * +-------+------------------------------------------------------+--------+ * | UN | MOUNTED | UN | * |MOUNTED| | MOUNTED| * +-------+------------------------------------------------------+--------+ * | ^--------+ +------+ +------+ +------+ +--------^ | * | | | | | | | | | | | | * | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 | * | | | |PROPS | | PROPS| | STATE| |MOUNTING| | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +--------+ +------+ +------+ +------+ +--------+ | * | | | | * +-------+------------------------------------------------------+--------+ */ var CompositeLifeCycle = keyMirror({ /** * Components in the process of being mounted respond to state changes * differently. */ MOUNTING: null, /** * Components in the process of being unmounted are guarded against state * changes. */ UNMOUNTING: null, /** * Components that are mounted and receiving new props respond to state * changes differently. */ RECEIVING_PROPS: null, /** * Components that are mounted and receiving new state are guarded against * additional state changes. */ RECEIVING_STATE: null }); /** * @lends {ReactCompositeComponent.prototype} */ var ReactCompositeComponentMixin = { /** * Base constructor for all composite component. * * @param {?object} initialProps * @param {*} children * @final * @internal */ construct: function(initialProps, children) { // Children can be either an array or more than one argument ReactComponent.Mixin.construct.apply(this, arguments); this.state = null; this._pendingState = null; this.context = this._processContext(ReactContext.current); this._currentContext = ReactContext.current; this._pendingContext = null; this._compositeLifeCycleState = null; }, /** * Checks whether or not this composite component is mounted. * @return {boolean} True if mounted, false otherwise. * @protected * @final */ isMounted: function() { return ReactComponent.Mixin.isMounted.call(this) && this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING; }, /** * Initializes the component, renders markup, and registers event listeners. * * @param {string} rootID DOM ID of the root node. * @param {ReactReconcileTransaction} transaction * @param {number} mountDepth number of components in the owner hierarchy * @return {?string} Rendered markup to be inserted into the DOM. * @final * @internal */ mountComponent: ReactPerf.measure( 'ReactCompositeComponent', 'mountComponent', function(rootID, transaction, mountDepth) { ReactComponent.Mixin.mountComponent.call( this, rootID, transaction, mountDepth ); this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null; this.props = this._processProps(this.props); if (this.__reactAutoBindMap) { this._bindAutoBindMethods(); } this.state = this.getInitialState ? this.getInitialState() : null; ("production" !== process.env.NODE_ENV ? invariant( typeof this.state === 'object' && !Array.isArray(this.state), '%s.getInitialState(): must return an object or null', this.constructor.displayName || 'ReactCompositeComponent' ) : invariant(typeof this.state === 'object' && !Array.isArray(this.state))); this._pendingState = null; this._pendingForceUpdate = false; if (this.componentWillMount) { this.componentWillMount(); // When mounting, calls to `setState` by `componentWillMount` will set // `this._pendingState` without triggering a re-render. if (this._pendingState) { this.state = this._pendingState; this._pendingState = null; } } this._renderedComponent = this._renderValidatedComponent(); // Done with mounting, `setState` will now trigger UI changes. this._compositeLifeCycleState = null; var markup = this._renderedComponent.mountComponent( rootID, transaction, mountDepth + 1 ); if (this.componentDidMount) { transaction.getReactMountReady().enqueue(this, this.componentDidMount); } return markup; } ), /** * Releases any resources allocated by `mountComponent`. * * @final * @internal */ unmountComponent: function() { this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; if (this.componentWillUnmount) { this.componentWillUnmount(); } this._compositeLifeCycleState = null; this._defaultProps = null; this._renderedComponent.unmountComponent(); this._renderedComponent = null; ReactComponent.Mixin.unmountComponent.call(this); if (this.refs) { this.refs = null; } // Some existing components rely on this.props even after they've been // destroyed (in event handlers). // TODO: this.props = null; // TODO: this.state = null; }, /** * Sets a subset of the state. Always use this or `replaceState` to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * @param {object} partialState Next partial state to be merged with state. * @param {?function} callback Called after state is updated. * @final * @protected */ setState: function(partialState, callback) { ("production" !== process.env.NODE_ENV ? invariant( typeof partialState === 'object' || partialState == null, 'setState(...): takes an object of state variables to update.' ) : invariant(typeof partialState === 'object' || partialState == null)); if ("production" !== process.env.NODE_ENV) { if (partialState == null) { console.warn( 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().' ); } } // Merge with `_pendingState` if it exists, otherwise with existing state. this.replaceState( merge(this._pendingState || this.state, partialState), callback ); }, /** * Replaces all of the state. Always use this or `setState` to mutate state. * You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * @param {object} completeState Next state. * @param {?function} callback Called after state is updated. * @final * @protected */ replaceState: function(completeState, callback) { validateLifeCycleOnReplaceState(this); this._pendingState = completeState; ReactUpdates.enqueueUpdate(this, callback); }, /** * Filters the context object to only contain keys specified in * `contextTypes`, and asserts that they are valid. * * @param {object} context * @return {?object} * @private */ _processContext: function(context) { var maskedContext = null; var contextTypes = this.constructor.contextTypes; if (contextTypes) { maskedContext = {}; for (var contextName in contextTypes) { maskedContext[contextName] = context[contextName]; } if ("production" !== process.env.NODE_ENV) { this._checkPropTypes( contextTypes, maskedContext, ReactPropTypeLocations.context ); } } return maskedContext; }, /** * @param {object} currentContext * @return {object} * @private */ _processChildContext: function(currentContext) { var childContext = this.getChildContext && this.getChildContext(); var displayName = this.constructor.displayName || 'ReactCompositeComponent'; if (childContext) { ("production" !== process.env.NODE_ENV ? invariant( typeof this.constructor.childContextTypes === 'object', '%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', displayName ) : invariant(typeof this.constructor.childContextTypes === 'object')); if ("production" !== process.env.NODE_ENV) { this._checkPropTypes( this.constructor.childContextTypes, childContext, ReactPropTypeLocations.childContext ); } for (var name in childContext) { ("production" !== process.env.NODE_ENV ? invariant( name in this.constructor.childContextTypes, '%s.getChildContext(): key "%s" is not defined in childContextTypes.', displayName, name ) : invariant(name in this.constructor.childContextTypes)); } return merge(currentContext, childContext); } return currentContext; }, /** * Processes props by setting default values for unspecified props and * asserting that the props are valid. Does not mutate its argument; returns * a new props object with defaults merged in. * * @param {object} newProps * @return {object} * @private */ _processProps: function(newProps) { var props = merge(newProps); var defaultProps = this._defaultProps; for (var propName in defaultProps) { if (typeof props[propName] === 'undefined') { props[propName] = defaultProps[propName]; } } if ("production" !== process.env.NODE_ENV) { var propTypes = this.constructor.propTypes; if (propTypes) { this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop); } } return props; }, /** * Assert that the props are valid * * @param {object} propTypes Map of prop name to a ReactPropType * @param {object} props * @param {string} location e.g. "prop", "context", "child context" * @private */ _checkPropTypes: function(propTypes, props, location) { var componentName = this.constructor.displayName; for (var propName in propTypes) { if (propTypes.hasOwnProperty(propName)) { propTypes[propName](props, propName, componentName, location); } } }, performUpdateIfNecessary: function() { var compositeLifeCycleState = this._compositeLifeCycleState; // Do not trigger a state transition if we are in the middle of mounting or // receiving props because both of those will already be doing this. if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { return; } ReactComponent.Mixin.performUpdateIfNecessary.call(this); }, /** * If any of `_pendingProps`, `_pendingState`, or `_pendingForceUpdate` is * set, update the component. * * @param {ReactReconcileTransaction} transaction * @internal */ _performUpdateIfNecessary: function(transaction) { if (this._pendingProps == null && this._pendingState == null && this._pendingContext == null && !this._pendingForceUpdate) { return; } var nextFullContext = this._pendingContext || this._currentContext; var nextContext = this._processContext(nextFullContext); this._pendingContext = null; var nextProps = this.props; if (this._pendingProps != null) { nextProps = this._processProps(this._pendingProps); this._pendingProps = null; this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; if (this.componentWillReceiveProps) { this.componentWillReceiveProps(nextProps, nextContext); } } this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE; // Unlike props, state, and context, we specifically don't want to set // _pendingOwner to null here because it's possible for a component to have // a null owner, so we instead make `this._owner === this._pendingOwner` // mean that there's no owner change pending. var nextOwner = this._pendingOwner; var nextState = this._pendingState || this.state; this._pendingState = null; try { if (this._pendingForceUpdate || !this.shouldComponentUpdate || this.shouldComponentUpdate(nextProps, nextState, nextContext)) { this._pendingForceUpdate = false; // Will set `this.props`, `this.state` and `this.context`. this._performComponentUpdate( nextProps, nextOwner, nextState, nextFullContext, nextContext, transaction ); } else { // If it's determined that a component should not update, we still want // to set props and state. this.props = nextProps; this._owner = nextOwner; this.state = nextState; this._currentContext = nextFullContext; this.context = nextContext; } } finally { this._compositeLifeCycleState = null; } }, /** * Merges new props and state, notifies delegate methods of update and * performs update. * * @param {object} nextProps Next object to set as properties. * @param {?ReactComponent} nextOwner Next component to set as owner * @param {?object} nextState Next object to set as state. * @param {?object} nextFullContext Next object to set as _currentContext. * @param {?object} nextContext Next object to set as context. * @param {ReactReconcileTransaction} transaction * @private */ _performComponentUpdate: function( nextProps, nextOwner, nextState, nextFullContext, nextContext, transaction ) { var prevProps = this.props; var prevOwner = this._owner; var prevState = this.state; var prevContext = this.context; if (this.componentWillUpdate) { this.componentWillUpdate(nextProps, nextState, nextContext); } this.props = nextProps; this._owner = nextOwner; this.state = nextState; this._currentContext = nextFullContext; this.context = nextContext; this.updateComponent( transaction, prevProps, prevOwner, prevState, prevContext ); if (this.componentDidUpdate) { transaction.getReactMountReady().enqueue( this, this.componentDidUpdate.bind(this, prevProps, prevState, prevContext) ); } }, receiveComponent: function(nextComponent, transaction) { if (nextComponent === this) { // Since props and context are immutable after the component is // mounted, we can do a cheap identity compare here to determine // if this is a superfluous reconcile. return; } this._pendingContext = nextComponent._currentContext; ReactComponent.Mixin.receiveComponent.call( this, nextComponent, transaction ); }, /** * 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 * @param {object} prevProps * @param {?ReactComponent} prevOwner * @param {?object} prevState * @param {?object} prevContext * @internal * @overridable */ updateComponent: ReactPerf.measure( 'ReactCompositeComponent', 'updateComponent', function(transaction, prevProps, prevOwner, prevState, prevContext) { ReactComponent.Mixin.updateComponent.call( this, transaction, prevProps, prevOwner ); var prevComponent = this._renderedComponent; var nextComponent = this._renderValidatedComponent(); if (shouldUpdateReactComponent(prevComponent, nextComponent)) { prevComponent.receiveComponent(nextComponent, transaction); } else { // These two IDs are actually the same! But nothing should rely on that. var thisID = this._rootNodeID; var prevComponentID = prevComponent._rootNodeID; prevComponent.unmountComponent(); this._renderedComponent = nextComponent; var nextMarkup = nextComponent.mountComponent( thisID, transaction, this._mountDepth + 1 ); ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID( prevComponentID, nextMarkup ); } } ), /** * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component's state has changed but `setState` was not called. * * This will not invoke `shouldUpdateComponent`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {?function} callback Called after update is complete. * @final * @protected */ forceUpdate: function(callback) { var compositeLifeCycleState = this._compositeLifeCycleState; ("production" !== process.env.NODE_ENV ? invariant( this.isMounted() || compositeLifeCycleState === CompositeLifeCycle.MOUNTING, 'forceUpdate(...): Can only force an update on mounted or mounting ' + 'components.' ) : invariant(this.isMounted() || compositeLifeCycleState === CompositeLifeCycle.MOUNTING)); ("production" !== process.env.NODE_ENV ? invariant( compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE && compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING, 'forceUpdate(...): Cannot force an update while unmounting component ' + 'or during an existing state transition (such as within `render`).' ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE && compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING)); this._pendingForceUpdate = true; ReactUpdates.enqueueUpdate(this, callback); }, /** * @private */ _renderValidatedComponent: ReactPerf.measure( 'ReactCompositeComponent', '_renderValidatedComponent', function() { var renderedComponent; var previousContext = ReactContext.current; ReactContext.current = this._processChildContext(this._currentContext); ReactCurrentOwner.current = this; try { renderedComponent = this.render(); } finally { ReactContext.current = previousContext; ReactCurrentOwner.current = null; } ("production" !== process.env.NODE_ENV ? invariant( ReactComponent.isValidComponent(renderedComponent), '%s.render(): A valid ReactComponent must be returned. You may have ' + 'returned null, undefined, an array, or some other invalid object.', this.constructor.displayName || 'ReactCompositeComponent' ) : invariant(ReactComponent.isValidComponent(renderedComponent))); return renderedComponent; } ), /** * @private */ _bindAutoBindMethods: function() { for (var autoBindKey in this.__reactAutoBindMap) { if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) { continue; } var method = this.__reactAutoBindMap[autoBindKey]; this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard( method, this.constructor.displayName + '.' + autoBindKey )); } }, /** * Binds a method to the component. * * @param {function} method Method to be bound. * @private */ _bindAutoBindMethod: function(method) { var component = this; var boundMethod = function() { return method.apply(component, arguments); }; if ("production" !== process.env.NODE_ENV) { boundMethod.__reactBoundContext = component; boundMethod.__reactBoundMethod = method; boundMethod.__reactBoundArguments = null; var componentName = component.constructor.displayName; var _bind = boundMethod.bind; boundMethod.bind = function(newThis ) {var args=Array.prototype.slice.call(arguments,1); // 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) { console.warn( 'bind(): React component methods may only be bound to the ' + 'component instance. See ' + componentName ); } else if (!args.length) { console.warn( '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 ' + componentName ); return boundMethod; } var reboundMethod = _bind.apply(boundMethod, arguments); reboundMethod.__reactBoundContext = component; reboundMethod.__reactBoundMethod = method; reboundMethod.__reactBoundArguments = args; return reboundMethod; }; } return boundMethod; } }; var ReactCompositeComponentBase = function() {}; mixInto(ReactCompositeComponentBase, ReactComponent.Mixin); mixInto(ReactCompositeComponentBase, ReactOwner.Mixin); mixInto(ReactCompositeComponentBase, ReactPropTransferer.Mixin); mixInto(ReactCompositeComponentBase, ReactCompositeComponentMixin); /** * Checks if a value is a valid component constructor. * * @param {*} * @return {boolean} * @public */ function isValidClass(componentClass) { return componentClass instanceof Function && 'componentConstructor' in componentClass && componentClass.componentConstructor instanceof Function; } /** * Module for creating composite components. * * @class ReactCompositeComponent * @extends ReactComponent * @extends ReactOwner * @extends ReactPropTransferer */ var ReactCompositeComponent = { LifeCycle: CompositeLifeCycle, Base: ReactCompositeComponentBase, /** * Creates a composite component class given a class specification. * * @param {object} spec Class specification (which must define `render`). * @return {function} Component constructor function. * @public */ createClass: function(spec) { var Constructor = function() {}; Constructor.prototype = new ReactCompositeComponentBase(); Constructor.prototype.constructor = Constructor; var ConvenienceConstructor = function(props, children) { var instance = new Constructor(); instance.construct.apply(instance, arguments); return instance; }; ConvenienceConstructor.componentConstructor = Constructor; Constructor.ConvenienceConstructor = ConvenienceConstructor; ConvenienceConstructor.originalSpec = spec; mixSpecIntoComponent(ConvenienceConstructor, spec); ("production" !== process.env.NODE_ENV ? invariant( Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.' ) : invariant(Constructor.prototype.render)); if ("production" !== process.env.NODE_ENV) { if (Constructor.prototype.componentShouldUpdate) { console.warn( (spec.displayName || '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.' ); } } // Expose the convience constructor on the prototype so that it can be // easily accessed on descriptors. E.g. <Foo />.type === Foo.type and for // static methods like <Foo />.type.staticMethod(); // This should not be named constructor since this may not be the function // that created the descriptor, and it may not even be a constructor. ConvenienceConstructor.type = Constructor; Constructor.prototype.type = Constructor; // Reduce time spent doing lookups by setting these on the prototype. for (var methodName in ReactCompositeComponentInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } if ("production" !== process.env.NODE_ENV) { Constructor.prototype = createMountWarningMembrane(Constructor.prototype); } return ConvenienceConstructor; }, isValidClass: isValidClass }; module.exports = ReactCompositeComponent;