/** * Copyright 2015, 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. * * @providesModule ReactUpdateQueue */ 'use strict'; var ReactLifeCycle = require("./ReactLifeCycle"); var ReactCurrentOwner = require("./ReactCurrentOwner"); var ReactElement = require("./ReactElement"); var ReactInstanceMap = require("./ReactInstanceMap"); var ReactUpdates = require("./ReactUpdates"); var assign = require("./Object.assign"); var invariant = require("./invariant"); var warning = require("./warning"); function enqueueUpdate(internalInstance) { if (internalInstance !== ReactLifeCycle.currentlyMountingInstance) { // If we're in a componentWillMount handler, don't enqueue a rerender // because ReactUpdates assumes we're in a browser context (which is // wrong for server rendering) and we're about to do a render anyway. // See bug in #1740. ReactUpdates.enqueueUpdate(internalInstance); } } function getInternalInstanceReadyForUpdate(publicInstance, callerName) { ("production" !== process.env.NODE_ENV ? invariant( ReactCurrentOwner.current == null, '%s(...): Cannot update during an existing state transition ' + '(such as within `render`). Render methods should be a pure function ' + 'of props and state.', callerName ) : invariant(ReactCurrentOwner.current == null)); var internalInstance = ReactInstanceMap.get(publicInstance); if (!internalInstance) { if ("production" !== process.env.NODE_ENV) { // Only warn when we have a callerName. Otherwise we should be silent. // We're probably calling from enqueueCallback. We don't want to warn // there because we already warned for the corresponding lifecycle method. ("production" !== process.env.NODE_ENV ? warning( !callerName, '%s(...): Can only update a mounted or mounting component. ' + 'This usually means you called %s() on an unmounted ' + 'component. This is a no-op.', callerName, callerName ) : null); } return null; } if (internalInstance === ReactLifeCycle.currentlyUnmountingInstance) { return null; } return internalInstance; } /** * ReactUpdateQueue allows for state updates to be scheduled into a later * reconciliation step. */ var ReactUpdateQueue = { /** * Enqueue a callback that will be executed after all the pending updates * have processed. * * @param {ReactClass} publicInstance The instance to use as `this` context. * @param {?function} callback Called after state is updated. * @internal */ enqueueCallback: function(publicInstance, callback) { ("production" !== process.env.NODE_ENV ? invariant( typeof callback === 'function', 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' + 'isn\'t callable.' ) : invariant(typeof callback === 'function')); var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); // Previously we would throw an error if we didn't have an internal // instance. Since we want to make it a no-op instead, we mirror the same // behavior we have in other enqueue* methods. // We also need to ignore callbacks in componentWillMount. See // enqueueUpdates. if (!internalInstance || internalInstance === ReactLifeCycle.currentlyMountingInstance) { return null; } if (internalInstance._pendingCallbacks) { internalInstance._pendingCallbacks.push(callback); } else { internalInstance._pendingCallbacks = [callback]; } // TODO: The callback here is ignored when setState is called from // componentWillMount. Either fix it or disallow doing so completely in // favor of getInitialState. Alternatively, we can disallow // componentWillMount during server-side rendering. enqueueUpdate(internalInstance); }, enqueueCallbackInternal: function(internalInstance, callback) { ("production" !== process.env.NODE_ENV ? invariant( typeof callback === 'function', 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' + 'isn\'t callable.' ) : invariant(typeof callback === 'function')); if (internalInstance._pendingCallbacks) { internalInstance._pendingCallbacks.push(callback); } else { internalInstance._pendingCallbacks = [callback]; } enqueueUpdate(internalInstance); }, /** * 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 {ReactClass} publicInstance The instance that should rerender. * @internal */ enqueueForceUpdate: function(publicInstance) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'forceUpdate' ); if (!internalInstance) { return; } internalInstance._pendingForceUpdate = true; enqueueUpdate(internalInstance); }, /** * 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 {ReactClass} publicInstance The instance that should rerender. * @param {object} completeState Next state. * @internal */ enqueueReplaceState: function(publicInstance, completeState) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'replaceState' ); if (!internalInstance) { return; } internalInstance._pendingStateQueue = [completeState]; internalInstance._pendingReplaceState = true; enqueueUpdate(internalInstance); }, /** * Sets a subset of the state. This only exists because _pendingState is * internal. This provides a merging strategy that is not available to deep * properties which is confusing. TODO: Expose pendingState or don't use it * during the merge. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialState Next partial state to be merged with state. * @internal */ enqueueSetState: function(publicInstance, partialState) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'setState' ); if (!internalInstance) { return; } var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); enqueueUpdate(internalInstance); }, /** * Sets a subset of the props. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialProps Subset of the next props. * @internal */ enqueueSetProps: function(publicInstance, partialProps) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'setProps' ); if (!internalInstance) { return; } ("production" !== process.env.NODE_ENV ? invariant( internalInstance._isTopLevel, 'setProps(...): You called `setProps` on a ' + 'component with a parent. This is an anti-pattern since props will ' + 'get reactively updated when rendered. Instead, change the owner\'s ' + '`render` method to pass the correct value as props to the component ' + 'where it is created.' ) : invariant(internalInstance._isTopLevel)); // Merge with the pending element if it exists, otherwise with existing // element props. var element = internalInstance._pendingElement || internalInstance._currentElement; var props = assign({}, element.props, partialProps); internalInstance._pendingElement = ReactElement.cloneAndReplaceProps( element, props ); enqueueUpdate(internalInstance); }, /** * Replaces all of the props. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} props New props. * @internal */ enqueueReplaceProps: function(publicInstance, props) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, 'replaceProps' ); if (!internalInstance) { return; } ("production" !== process.env.NODE_ENV ? invariant( internalInstance._isTopLevel, 'replaceProps(...): You called `replaceProps` on a ' + 'component with a parent. This is an anti-pattern since props will ' + 'get reactively updated when rendered. Instead, change the owner\'s ' + '`render` method to pass the correct value as props to the component ' + 'where it is created.' ) : invariant(internalInstance._isTopLevel)); // Merge with the pending element if it exists, otherwise with existing // element props. var element = internalInstance._pendingElement || internalInstance._currentElement; internalInstance._pendingElement = ReactElement.cloneAndReplaceProps( element, props ); enqueueUpdate(internalInstance); }, enqueueElementInternal: function(internalInstance, newElement) { internalInstance._pendingElement = newElement; enqueueUpdate(internalInstance); } }; module.exports = ReactUpdateQueue;