UNPKG

9.23 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2015-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant');
12
13var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
14var ReactInstanceMap = require('./ReactInstanceMap');
15var ReactInstrumentation = require('./ReactInstrumentation');
16var ReactUpdates = require('./ReactUpdates');
17
18var invariant = require('fbjs/lib/invariant');
19var warning = require('fbjs/lib/warning');
20
21function enqueueUpdate(internalInstance) {
22 ReactUpdates.enqueueUpdate(internalInstance);
23}
24
25function formatUnexpectedArgument(arg) {
26 var type = typeof arg;
27 if (type !== 'object') {
28 return type;
29 }
30 var displayName = arg.constructor && arg.constructor.name || type;
31 var keys = Object.keys(arg);
32 if (keys.length > 0 && keys.length < 20) {
33 return displayName + ' (keys: ' + keys.join(', ') + ')';
34 }
35 return displayName;
36}
37
38function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
39 var internalInstance = ReactInstanceMap.get(publicInstance);
40 if (!internalInstance) {
41 if (process.env.NODE_ENV !== 'production') {
42 var ctor = publicInstance.constructor;
43 // Only warn when we have a callerName. Otherwise we should be silent.
44 // We're probably calling from enqueueCallback. We don't want to warn
45 // there because we already warned for the corresponding lifecycle method.
46 process.env.NODE_ENV !== 'production' ? 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. Please check the code for the %s component.', callerName, callerName, ctor && (ctor.displayName || ctor.name) || 'ReactClass') : void 0;
47 }
48 return null;
49 }
50
51 if (process.env.NODE_ENV !== 'production') {
52 process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '%s(...): Cannot update during an existing state transition (such as ' + "within `render` or another component's constructor). Render methods " + 'should be a pure function of props and state; constructor ' + 'side-effects are an anti-pattern, but can be moved to ' + '`componentWillMount`.', callerName) : void 0;
53 }
54
55 return internalInstance;
56}
57
58/**
59 * ReactUpdateQueue allows for state updates to be scheduled into a later
60 * reconciliation step.
61 */
62var ReactUpdateQueue = {
63 /**
64 * Checks whether or not this composite component is mounted.
65 * @param {ReactClass} publicInstance The instance we want to test.
66 * @return {boolean} True if mounted, false otherwise.
67 * @protected
68 * @final
69 */
70 isMounted: function (publicInstance) {
71 if (process.env.NODE_ENV !== 'production') {
72 var owner = ReactCurrentOwner.current;
73 if (owner !== null) {
74 process.env.NODE_ENV !== 'production' ? warning(owner._warnedAboutRefsInRender, '%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', owner.getName() || 'A component') : void 0;
75 owner._warnedAboutRefsInRender = true;
76 }
77 }
78 var internalInstance = ReactInstanceMap.get(publicInstance);
79 if (internalInstance) {
80 // During componentWillMount and render this will still be null but after
81 // that will always render to something. At least for now. So we can use
82 // this hack.
83 return !!internalInstance._renderedComponent;
84 } else {
85 return false;
86 }
87 },
88
89 /**
90 * Enqueue a callback that will be executed after all the pending updates
91 * have processed.
92 *
93 * @param {ReactClass} publicInstance The instance to use as `this` context.
94 * @param {?function} callback Called after state is updated.
95 * @param {string} callerName Name of the calling function in the public API.
96 * @internal
97 */
98 enqueueCallback: function (publicInstance, callback, callerName) {
99 ReactUpdateQueue.validateCallback(callback, callerName);
100 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
101
102 // Previously we would throw an error if we didn't have an internal
103 // instance. Since we want to make it a no-op instead, we mirror the same
104 // behavior we have in other enqueue* methods.
105 // We also need to ignore callbacks in componentWillMount. See
106 // enqueueUpdates.
107 if (!internalInstance) {
108 return null;
109 }
110
111 if (internalInstance._pendingCallbacks) {
112 internalInstance._pendingCallbacks.push(callback);
113 } else {
114 internalInstance._pendingCallbacks = [callback];
115 }
116 // TODO: The callback here is ignored when setState is called from
117 // componentWillMount. Either fix it or disallow doing so completely in
118 // favor of getInitialState. Alternatively, we can disallow
119 // componentWillMount during server-side rendering.
120 enqueueUpdate(internalInstance);
121 },
122
123 enqueueCallbackInternal: function (internalInstance, callback) {
124 if (internalInstance._pendingCallbacks) {
125 internalInstance._pendingCallbacks.push(callback);
126 } else {
127 internalInstance._pendingCallbacks = [callback];
128 }
129 enqueueUpdate(internalInstance);
130 },
131
132 /**
133 * Forces an update. This should only be invoked when it is known with
134 * certainty that we are **not** in a DOM transaction.
135 *
136 * You may want to call this when you know that some deeper aspect of the
137 * component's state has changed but `setState` was not called.
138 *
139 * This will not invoke `shouldComponentUpdate`, but it will invoke
140 * `componentWillUpdate` and `componentDidUpdate`.
141 *
142 * @param {ReactClass} publicInstance The instance that should rerender.
143 * @internal
144 */
145 enqueueForceUpdate: function (publicInstance) {
146 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'forceUpdate');
147
148 if (!internalInstance) {
149 return;
150 }
151
152 internalInstance._pendingForceUpdate = true;
153
154 enqueueUpdate(internalInstance);
155 },
156
157 /**
158 * Replaces all of the state. Always use this or `setState` to mutate state.
159 * You should treat `this.state` as immutable.
160 *
161 * There is no guarantee that `this.state` will be immediately updated, so
162 * accessing `this.state` after calling this method may return the old value.
163 *
164 * @param {ReactClass} publicInstance The instance that should rerender.
165 * @param {object} completeState Next state.
166 * @internal
167 */
168 enqueueReplaceState: function (publicInstance, completeState, callback) {
169 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'replaceState');
170
171 if (!internalInstance) {
172 return;
173 }
174
175 internalInstance._pendingStateQueue = [completeState];
176 internalInstance._pendingReplaceState = true;
177
178 // Future-proof 15.5
179 if (callback !== undefined && callback !== null) {
180 ReactUpdateQueue.validateCallback(callback, 'replaceState');
181 if (internalInstance._pendingCallbacks) {
182 internalInstance._pendingCallbacks.push(callback);
183 } else {
184 internalInstance._pendingCallbacks = [callback];
185 }
186 }
187
188 enqueueUpdate(internalInstance);
189 },
190
191 /**
192 * Sets a subset of the state. This only exists because _pendingState is
193 * internal. This provides a merging strategy that is not available to deep
194 * properties which is confusing. TODO: Expose pendingState or don't use it
195 * during the merge.
196 *
197 * @param {ReactClass} publicInstance The instance that should rerender.
198 * @param {object} partialState Next partial state to be merged with state.
199 * @internal
200 */
201 enqueueSetState: function (publicInstance, partialState) {
202 if (process.env.NODE_ENV !== 'production') {
203 ReactInstrumentation.debugTool.onSetState();
204 process.env.NODE_ENV !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : void 0;
205 }
206
207 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
208
209 if (!internalInstance) {
210 return;
211 }
212
213 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
214 queue.push(partialState);
215
216 enqueueUpdate(internalInstance);
217 },
218
219 enqueueElementInternal: function (internalInstance, nextElement, nextContext) {
220 internalInstance._pendingElement = nextElement;
221 // TODO: introduce _pendingContext instead of setting it directly.
222 internalInstance._context = nextContext;
223 enqueueUpdate(internalInstance);
224 },
225
226 validateCallback: function (callback, callerName) {
227 !(!callback || typeof callback === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s(...): Expected the last optional `callback` argument to be a function. Instead received: %s.', callerName, formatUnexpectedArgument(callback)) : _prodInvariant('122', callerName, formatUnexpectedArgument(callback)) : void 0;
228 }
229};
230
231module.exports = ReactUpdateQueue;
\No newline at end of file