@samitouri / QOS-React-2 / commits / ed7332c749

Extract setState, setProps etc into ReactUpdateQueue

I originally did this work so that we could allow setState to be called before the internal ReactCompositeComponent was constructed. It's unlikely that we'll go down that route now but this still seems like a better abstraction. It communicates that this is not immediately updating an object oriented class. It's just a queue which a minor optimization. It also avoids bloating the ReactCompositeComponent file. Since they both depend on the life cycle I break that out into a common shared dependency. In a follow up I'll refactor the life-cycle management.

Sebastian Markbage committed Jan 27, 2015 at 00:55 UTC ed7332c74921874cdcb3cce629bb5bd8c95a7968
10 files changed +377 -279
src/browser/ui/ReactMount.js
+5 -2
@@ -22,6 +22,7 @@ var ReactInstanceMap = require('ReactInstanceMap');
22 var ReactMarkupChecksum = require('ReactMarkupChecksum');
23 var ReactPerf = require('ReactPerf');
24 var ReactReconciler = require('ReactReconciler');
25 +var ReactUpdateQueue = require('ReactUpdateQueue');
26 var ReactUpdates = require('ReactUpdates');
27
28 var emptyObject = require('emptyObject');
@@ -299,9 +300,11 @@ var ReactMount = {
300 ReactElementValidator.checkAndWarnForMutatedProps(nextElement);
301 }
302
302 - var nextProps = nextElement.props;
303 ReactMount.scrollMonitor(container, function() {
304 - prevComponent.replaceProps(nextProps, callback);
304 + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
305 + if (callback) {
306 + ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
307 + }
308 });
309
310 if (__DEV__) {
src/classic/class/ReactClass.js
+13 -24
@@ -17,6 +17,7 @@ var ReactErrorUtils = require('ReactErrorUtils');
17 var ReactInstanceMap = require('ReactInstanceMap');
18 var ReactPropTypeLocations = require('ReactPropTypeLocations');
19 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
20 +var ReactUpdateQueue = require('ReactUpdateQueue');
21
22 var assign = require('Object.assign');
23 var invariant = require('invariant');
@@ -719,16 +720,10 @@ var ReactClassMixin = {
720 * type signature and the only use case for this, is to avoid that.
721 */
722 replaceState: function(newState, callback) {
722 - var internalInstance = ReactInstanceMap.get(this);
723 - invariant(
724 - internalInstance,
725 - 'replaceState(...): Can only update a mounted or mounting component. ' +
726 - 'This usually means you called replaceState() on an unmounted component.'
727 - );
728 - internalInstance.replaceState(
729 - newState,
730 - callback
731 - );
723 + ReactUpdateQueue.enqueueReplaceState(this, newState);
724 + if (callback) {
725 + ReactUpdateQueue.enqueueCallback(this, callback);
726 + }
727 },
728
729 /**
@@ -754,16 +749,10 @@ var ReactClassMixin = {
749 * @deprecated
750 */
751 setProps: function(partialProps, callback) {
757 - var internalInstance = ReactInstanceMap.get(this);
758 - invariant(
759 - internalInstance,
760 - 'setProps(...): Can only update a mounted or mounting component. ' +
761 - 'This usually means you called setProps() on an unmounted component.'
762 - );
763 - internalInstance.setProps(
764 - partialProps,
765 - callback
766 - );
752 + ReactUpdateQueue.enqueueSetProps(this, partialProps);
753 + if (callback) {
754 + ReactUpdateQueue.enqueueCallback(this, callback);
755 + }
756 },
757
758 /**
@@ -776,10 +765,10 @@ var ReactClassMixin = {
765 * @deprecated
766 */
767 replaceProps: function(newProps, callback) {
779 - ReactInstanceMap.get(this).replaceProps(
780 - newProps,
781 - callback
782 - );
768 + ReactUpdateQueue.enqueueReplaceProps(this, newProps);
769 + if (callback) {
770 + ReactUpdateQueue.enqueueCallback(this, callback);
771 + }
772 }
773 };
774
src/core/ReactCompositeComponent.js
+9 -202
@@ -17,6 +17,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
17 var ReactElement = require('ReactElement');
18 var ReactElementValidator = require('ReactElementValidator');
19 var ReactInstanceMap = require('ReactInstanceMap');
20 +var ReactLifeCycle = require('ReactLifeCycle');
21 var ReactNativeComponent = require('ReactNativeComponent');
22 var ReactPerf = require('ReactPerf');
23 var ReactPropTypeLocations = require('ReactPropTypeLocations');
@@ -27,8 +28,6 @@ var ReactUpdates = require('ReactUpdates');
28 var assign = require('Object.assign');
29 var emptyObject = require('emptyObject');
30 var invariant = require('invariant');
30 -var keyMirror = require('keyMirror');
31 -var monitorCodeUse = require('monitorCodeUse');
31 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
32 var warning = require('warning');
33
@@ -43,50 +42,6 @@ function getDeclarationErrorAddendum(component) {
42 return '';
43 }
44
46 -/**
47 - * `ReactCompositeComponent` maintains an auxiliary life cycle state in
48 - * `this._compositeLifeCycleState` (which can be null).
49 - *
50 - * This is different from the life cycle state maintained by `ReactComponent`.
51 - * The following diagram shows how the states overlap in
52 - * time. There are times when the CompositeLifeCycle is null - at those times it
53 - * is only meaningful to look at ComponentLifeCycle alone.
54 - *
55 - * Top Row: ReactComponent.ComponentLifeCycle
56 - * Low Row: ReactComponent.CompositeLifeCycle
57 - *
58 - * +-------+---------------------------------+--------+
59 - * | UN | MOUNTED | UN |
60 - * |MOUNTED| | MOUNTED|
61 - * +-------+---------------------------------+--------+
62 - * | ^--------+ +-------+ +--------^ |
63 - * | | | | | | | |
64 - * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
65 - * | | | |PROPS | |MOUNTING| |
66 - * | | | | | | | |
67 - * | | | | | | | |
68 - * | +--------+ +-------+ +--------+ |
69 - * | | | |
70 - * +-------+---------------------------------+--------+
71 - */
72 -var CompositeLifeCycle = keyMirror({
73 - /**
74 - * Components in the process of being mounted respond to state changes
75 - * differently.
76 - */
77 - MOUNTING: null,
78 - /**
79 - * Components in the process of being unmounted are guarded against state
80 - * changes.
81 - */
82 - UNMOUNTING: null,
83 - /**
84 - * Components that are mounted and receiving new props respond to state
85 - * changes differently.
86 - */
87 - RECEIVING_PROPS: null
88 -});
89 -
45 /**
46 * An incrementing ID assigned to each component when it is mounted. This is
47 * used to enforce the order in which `ReactUpdates` updates dirty components.
@@ -112,6 +67,7 @@ var ReactCompositeComponentMixin = {
67 this._rootNodeID = null;
68 this._instance = null;
69
70 + // See ReactUpdateQueue
71 this._pendingElement = null;
72 this._pendingState = null;
73 this._pendingForceUpdate = false;
@@ -123,7 +79,7 @@ var ReactCompositeComponentMixin = {
79 this._mountOrder = 0;
80 this._isTopLevel = false;
81
126 - // See ReactUpdates.
82 + // See ReactUpdates and ReactUpdateQueue.
83 this._pendingCallbacks = null;
84 },
85
@@ -134,7 +90,7 @@ var ReactCompositeComponentMixin = {
90 * @final
91 */
92 isMounted: function() {
137 - return this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
93 + return this._compositeLifeCycleState !== ReactLifeCycle.MOUNTING;
94 },
95
96 /**
@@ -171,7 +127,7 @@ var ReactCompositeComponentMixin = {
127 // Store a reference from the instance back to the internal representation
128 ReactInstanceMap.set(inst, this);
129
174 - this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
130 + this._compositeLifeCycleState = ReactLifeCycle.MOUNTING;
131
132 if (__DEV__) {
133 this._warnIfContextsDiffer(this._currentElement._context, context);
@@ -265,7 +221,7 @@ var ReactCompositeComponentMixin = {
221 unmountComponent: function() {
222 var inst = this._instance;
223
268 - this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
224 + this._compositeLifeCycleState = ReactLifeCycle.UNMOUNTING;
225 if (inst.componentWillUnmount) {
226 inst.componentWillUnmount();
227 }
@@ -297,50 +253,6 @@ var ReactCompositeComponentMixin = {
253 // TODO: inst.context = null;
254 },
255
300 - /**
301 - * Sets a subset of the props.
302 - *
303 - * @param {object} partialProps Subset of the next props.
304 - * @param {?function} callback Called after props are updated.
305 - * @final
306 - * @public
307 - */
308 - setProps: function(partialProps, callback) {
309 - // Merge with the pending element if it exists, otherwise with existing
310 - // element props.
311 - var element = this._pendingElement || this._currentElement;
312 - this.replaceProps(
313 - assign({}, element.props, partialProps),
314 - callback
315 - );
316 - },
317 -
318 - /**
319 - * Replaces all of the props.
320 - *
321 - * @param {object} props New props.
322 - * @param {?function} callback Called after props are updated.
323 - * @final
324 - * @public
325 - */
326 - replaceProps: function(props, callback) {
327 - invariant(
328 - this._isTopLevel,
329 - 'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
330 - 'component with a parent. This is an anti-pattern since props will ' +
331 - 'get reactively updated when rendered. Instead, change the owner\'s ' +
332 - '`render` method to pass the correct value as props to the component ' +
333 - 'where it is created.'
334 - );
335 - // This is a deoptimized path. We optimize for always having an element.
336 - // This creates an extra internal element.
337 - this._pendingElement = ReactElement.cloneAndReplaceProps(
338 - this._pendingElement || this._currentElement,
339 - props
340 - );
341 - ReactUpdates.enqueueUpdate(this, callback);
342 - },
343 -
256 /**
257 * Schedule a partial update to the props. Only used for internal testing.
258 *
@@ -360,109 +272,6 @@ var ReactCompositeComponentMixin = {
272 ReactUpdates.enqueueUpdate(this, callback);
273 },
274
363 - /**
364 - * Sets a subset of the state. This only exists because _pendingState is
365 - * internal. This provides a merging strategy that is not available to deep
366 - * properties which is confusing. TODO: Expose pendingState or don't use it
367 - * during the merge.
368 - *
369 - * @param {object} partialState Next partial state to be merged with state.
370 - * @param {?function} callback Called after state is updated.
371 - * @final
372 - * @protected
373 - */
374 - setState: function(partialState, callback) {
375 - var compositeLifeCycleState = this._compositeLifeCycleState;
376 - invariant(
377 - ReactCurrentOwner.current == null,
378 - 'setState(...): Cannot update during an existing state transition ' +
379 - '(such as within `render`). Render methods should be a pure function ' +
380 - 'of props and state.'
381 - );
382 - invariant(
383 - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
384 - 'setState(...): Cannot call setState() on an unmounting component.'
385 - );
386 - // Merge with `_pendingState` if it exists, otherwise with existing state.
387 - this._pendingState = assign(
388 - {},
389 - this._pendingState || this._instance.state,
390 - partialState
391 - );
392 - if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
393 - // If we're in a componentWillMount handler, don't enqueue a rerender
394 - // because ReactUpdates assumes we're in a browser context (which is wrong
395 - // for server rendering) and we're about to do a render anyway.
396 - // TODO: The callback here is ignored when setState is called from
397 - // componentWillMount. Either fix it or disallow doing so completely in
398 - // favor of getInitialState.
399 - ReactUpdates.enqueueUpdate(this, callback);
400 - }
401 - },
402 -
403 - /**
404 - * Replaces all of the state. Always use this or `setState` to mutate state.
405 - * You should treat `this.state` as immutable.
406 - *
407 - * There is no guarantee that `this.state` will be immediately updated, so
408 - * accessing `this.state` after calling this method may return the old value.
409 - *
410 - * @param {object} completeState Next state.
411 - * @param {?function} callback Called after state is updated.
412 - * @final
413 - * @protected
414 - */
415 - replaceState: function(completeState, callback) {
416 - var compositeLifeCycleState = this._compositeLifeCycleState;
417 - invariant(
418 - ReactCurrentOwner.current == null,
419 - 'replaceState(...): Cannot update during an existing state transition ' +
420 - '(such as within `render`). Render methods should be a pure function ' +
421 - 'of props and state.'
422 - );
423 - invariant(
424 - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
425 - 'replaceState(...): Cannot call replaceState() on an unmounting ' +
426 - 'component.'
427 - );
428 - this._pendingState = completeState;
429 - if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
430 - // If we're in a componentWillMount handler, don't enqueue a rerender
431 - // because ReactUpdates assumes we're in a browser context (which is wrong
432 - // for server rendering) and we're about to do a render anyway.
433 - // TODO: The callback here is ignored when setState is called from
434 - // componentWillMount. Either fix it or disallow doing so completely in
435 - // favor of getInitialState.
436 - ReactUpdates.enqueueUpdate(this, callback);
437 - }
438 - },
439 -
440 - /**
441 - * Forces an update. This should only be invoked when it is known with
442 - * certainty that we are **not** in a DOM transaction.
443 - *
444 - * You may want to call this when you know that some deeper aspect of the
445 - * component's state has changed but `setState` was not called.
446 - *
447 - * This will not invoke `shouldUpdateComponent`, but it will invoke
448 - * `componentWillUpdate` and `componentDidUpdate`.
449 - *
450 - * @param {?function} callback Called after update is complete.isM
451 - * @final
452 - * @protected
453 - */
454 - forceUpdate: function(callback) {
455 - var compositeLifeCycleState = this._compositeLifeCycleState;
456 - invariant(
457 - compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
458 - compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
459 - 'forceUpdate(...): Cannot force an update while unmounting component ' +
460 - 'or during an existing state transition (such as within `render`).'
461 - );
462 - this._pendingForceUpdate = true;
463 - ReactUpdates.enqueueUpdate(this, callback);
464 - },
465 -
275 /**
276 * Filters the context object to only contain keys specified in
277 * `contextTypes`
@@ -626,8 +435,8 @@ var ReactCompositeComponentMixin = {
435 var compositeLifeCycleState = this._compositeLifeCycleState;
436 // Do not trigger a state transition if we are in the middle of mounting or
437 // receiving props because both of those will already be doing this.
629 - if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
630 - compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
438 + if (compositeLifeCycleState === ReactLifeCycle.MOUNTING ||
439 + compositeLifeCycleState === ReactLifeCycle.RECEIVING_PROPS) {
440 return;
441 }
442
@@ -743,7 +552,7 @@ var ReactCompositeComponentMixin = {
552 }
553 }
554
746 - this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
555 + this._compositeLifeCycleState = ReactLifeCycle.RECEIVING_PROPS;
556 if (inst.componentWillReceiveProps) {
557 inst.componentWillReceiveProps(nextProps, nextContext);
558 }
@@ -1003,8 +812,6 @@ ReactPerf.measureMethods(
812
813 var ReactCompositeComponent = {
814
1006 - LifeCycle: CompositeLifeCycle,
1007 -
815 Mixin: ReactCompositeComponentMixin
816
817 };
src/core/ReactLifeCycle.js new
+60
@@ -0,0 +1,60 @@
1 +/**
2 + * Copyright 2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactLifeCycle
10 + */
11 +
12 +"use strict";
13 +
14 +var keyMirror = require('keyMirror');
15 +
16 +/**
17 + * `ReactCompositeComponent` maintains an auxiliary life cycle state in
18 + * `this._compositeLifeCycleState` (which can be null).
19 + *
20 + * This is different from the life cycle state maintained by `ReactComponent`.
21 + * The following diagram shows how the states overlap in
22 + * time. There are times when the CompositeLifeCycle is null - at those times it
23 + * is only meaningful to look at ComponentLifeCycle alone.
24 + *
25 + * Top Row: ReactComponent.ComponentLifeCycle
26 + * Low Row: ReactComponent.CompositeLifeCycle
27 + *
28 + * +-------+---------------------------------+--------+
29 + * | UN | MOUNTED | UN |
30 + * |MOUNTED| | MOUNTED|
31 + * +-------+---------------------------------+--------+
32 + * | ^--------+ +-------+ +--------^ |
33 + * | | | | | | | |
34 + * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
35 + * | | | |PROPS | |MOUNTING| |
36 + * | | | | | | | |
37 + * | | | | | | | |
38 + * | +--------+ +-------+ +--------+ |
39 + * | | | |
40 + * +-------+---------------------------------+--------+
41 + */
42 +var ReactLifeCycle = keyMirror({
43 + /**
44 + * Components in the process of being mounted respond to state changes
45 + * differently.
46 + */
47 + MOUNTING: null,
48 + /**
49 + * Components in the process of being unmounted are guarded against state
50 + * changes.
51 + */
52 + UNMOUNTING: null,
53 + /**
54 + * Components that are mounted and receiving new props respond to state
55 + * changes differently.
56 + */
57 + RECEIVING_PROPS: null
58 +});
59 +
60 +module.exports = ReactLifeCycle;
src/core/ReactUpdateQueue.js new
+264
@@ -0,0 +1,264 @@
1 +/**
2 + * Copyright 2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactUpdateQueue
10 + */
11 +
12 +"use strict";
13 +
14 +var ReactLifeCycle = require('ReactLifeCycle');
15 +var ReactCurrentOwner = require('ReactCurrentOwner');
16 +var ReactElement = require('ReactElement');
17 +var ReactInstanceMap = require('ReactInstanceMap');
18 +var ReactUpdates = require('ReactUpdates');
19 +
20 +var assign = require('Object.assign');
21 +var invariant = require('invariant');
22 +
23 +function enqueueUpdate(internalInstance) {
24 + if (internalInstance._compositeLifeCycleState !== ReactLifeCycle.MOUNTING) {
25 + // If we're in a componentWillMount handler, don't enqueue a rerender
26 + // because ReactUpdates assumes we're in a browser context (which is
27 + // wrong for server rendering) and we're about to do a render anyway.
28 + // See bug in #1740.
29 + ReactUpdates.enqueueUpdate(internalInstance);
30 + }
31 +}
32 +
33 +function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
34 + invariant(
35 + ReactCurrentOwner.current == null,
36 + '%s(...): Cannot update during an existing state transition ' +
37 + '(such as within `render`). Render methods should be a pure function ' +
38 + 'of props and state.',
39 + callerName
40 + );
41 +
42 + var internalInstance = ReactInstanceMap.get(publicInstance);
43 + invariant(
44 + internalInstance,
45 + '%s(...): Can only update a mounted or mounting component. ' +
46 + 'This usually means you called %s() on an unmounted ' +
47 + 'component.',
48 + callerName,
49 + callerName
50 + );
51 + invariant(
52 + internalInstance._compositeLifeCycleState !==
53 + ReactLifeCycle.UNMOUNTING,
54 + '%s(...): Cannot call %s() on an unmounting component.',
55 + callerName,
56 + callerName
57 + );
58 + return internalInstance;
59 +}
60 +
61 +/**
62 + * ReactUpdateQueue allows for state updates to be scheduled into a later
63 + * reconciliation step.
64 + */
65 +var ReactUpdateQueue = {
66 +
67 + /**
68 + * Enqueue a callback that will be executed after all the pending updates
69 + * have processed.
70 + *
71 + * @param {ReactClass} publicInstance The instance to use as `this` context.
72 + * @param {?function} callback Called after state is updated.
73 + * @internal
74 + */
75 + enqueueCallback: function(publicInstance, callback) {
76 + invariant(
77 + typeof callback === 'function',
78 + 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
79 + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
80 + 'isn\'t callable.'
81 + );
82 + var internalInstance = ReactInstanceMap.get(publicInstance);
83 + invariant(
84 + internalInstance,
85 + 'Cannot enqueue a callback on an instance that is unmounted.'
86 + );
87 + if (internalInstance._compositeLifeCycleState === ReactLifeCycle.MOUNTING) {
88 + // Ignore callbacks in componentWillMount. See enqueueUpdate.
89 + return;
90 + }
91 + if (internalInstance._pendingCallbacks) {
92 + internalInstance._pendingCallbacks.push(callback);
93 + } else {
94 + internalInstance._pendingCallbacks = [callback];
95 + }
96 + // TODO: The callback here is ignored when setState is called from
97 + // componentWillMount. Either fix it or disallow doing so completely in
98 + // favor of getInitialState.
99 + enqueueUpdate(internalInstance);
100 + },
101 +
102 + enqueueCallbackInternal: function(internalInstance, callback) {
103 + invariant(
104 + typeof callback === "function",
105 + 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
106 + '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
107 + 'isn\'t callable.'
108 + );
109 + if (internalInstance._pendingCallbacks) {
110 + internalInstance._pendingCallbacks.push(callback);
111 + } else {
112 + internalInstance._pendingCallbacks = [callback];
113 + }
114 + },
115 +
116 + /**
117 + * Forces an update. This should only be invoked when it is known with
118 + * certainty that we are **not** in a DOM transaction.
119 + *
120 + * You may want to call this when you know that some deeper aspect of the
121 + * component's state has changed but `setState` was not called.
122 + *
123 + * This will not invoke `shouldUpdateComponent`, but it will invoke
124 + * `componentWillUpdate` and `componentDidUpdate`.
125 + *
126 + * @param {ReactClass} publicInstance The instance that should rerender.
127 + * @internal
128 + */
129 + enqueueForceUpdate: function(publicInstance) {
130 + var internalInstance = getInternalInstanceReadyForUpdate(
131 + publicInstance,
132 + 'forceUpdate'
133 + );
134 +
135 + internalInstance._pendingForceUpdate = true;
136 +
137 + enqueueUpdate(internalInstance);
138 + },
139 +
140 + /**
141 + * Replaces all of the state. Always use this or `setState` to mutate state.
142 + * You should treat `this.state` as immutable.
143 + *
144 + * There is no guarantee that `this.state` will be immediately updated, so
145 + * accessing `this.state` after calling this method may return the old value.
146 + *
147 + * @param {ReactClass} publicInstance The instance that should rerender.
148 + * @param {object} completeState Next state.
149 + * @internal
150 + */
151 + enqueueReplaceState: function(publicInstance, completeState) {
152 + var internalInstance = getInternalInstanceReadyForUpdate(
153 + publicInstance,
154 + 'replaceState'
155 + );
156 +
157 + internalInstance._pendingState = completeState;
158 +
159 + enqueueUpdate(internalInstance);
160 + },
161 +
162 + /**
163 + * Sets a subset of the state. This only exists because _pendingState is
164 + * internal. This provides a merging strategy that is not available to deep
165 + * properties which is confusing. TODO: Expose pendingState or don't use it
166 + * during the merge.
167 + *
168 + * @param {ReactClass} publicInstance The instance that should rerender.
169 + * @param {object} partialState Next partial state to be merged with state.
170 + * @internal
171 + */
172 + enqueueSetState: function(publicInstance, partialState) {
173 + var internalInstance = getInternalInstanceReadyForUpdate(
174 + publicInstance,
175 + 'setState'
176 + );
177 +
178 + // Merge with `_pendingState` if it exists, otherwise with existing state.
179 + internalInstance._pendingState = assign(
180 + {},
181 + internalInstance._pendingState || internalInstance._instance.state,
182 + partialState
183 + );
184 +
185 + enqueueUpdate(internalInstance);
186 + },
187 +
188 + /**
189 + * Sets a subset of the props.
190 + *
191 + * @param {ReactClass} publicInstance The instance that should rerender.
192 + * @param {object} partialProps Subset of the next props.
193 + * @internal
194 + */
195 + enqueueSetProps: function(publicInstance, partialProps) {
196 + var internalInstance = getInternalInstanceReadyForUpdate(
197 + publicInstance,
198 + 'setProps'
199 + );
200 +
201 + invariant(
202 + internalInstance._isTopLevel,
203 + 'setProps(...): You called `setProps` on a ' +
204 + 'component with a parent. This is an anti-pattern since props will ' +
205 + 'get reactively updated when rendered. Instead, change the owner\'s ' +
206 + '`render` method to pass the correct value as props to the component ' +
207 + 'where it is created.'
208 + );
209 +
210 + // Merge with the pending element if it exists, otherwise with existing
211 + // element props.
212 + var element = internalInstance._pendingElement ||
213 + internalInstance._currentElement;
214 + var props = assign({}, element.props, partialProps);
215 + internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
216 + element,
217 + props
218 + );
219 +
220 + enqueueUpdate(internalInstance);
221 + },
222 +
223 + /**
224 + * Replaces all of the props.
225 + *
226 + * @param {ReactClass} publicInstance The instance that should rerender.
227 + * @param {object} props New props.
228 + * @internal
229 + */
230 + enqueueReplaceProps: function(publicInstance, props) {
231 + var internalInstance = getInternalInstanceReadyForUpdate(
232 + publicInstance,
233 + 'replaceProps'
234 + );
235 +
236 + invariant(
237 + internalInstance._isTopLevel,
238 + 'replaceProps(...): You called `replaceProps` on a ' +
239 + 'component with a parent. This is an anti-pattern since props will ' +
240 + 'get reactively updated when rendered. Instead, change the owner\'s ' +
241 + '`render` method to pass the correct value as props to the component ' +
242 + 'where it is created.'
243 + );
244 +
245 + // Merge with the pending element if it exists, otherwise with existing
246 + // element props.
247 + var element = internalInstance._pendingElement ||
248 + internalInstance._currentElement;
249 + internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
250 + element,
251 + props
252 + );
253 +
254 + enqueueUpdate(internalInstance);
255 + },
256 +
257 + enqueueElementInternal: function(internalInstance, newElement) {
258 + internalInstance._pendingElement = newElement;
259 + enqueueUpdate(internalInstance);
260 + }
261 +
262 +};
263 +
264 +module.exports = ReactUpdateQueue;
src/core/ReactUpdates.js
+2 -16
@@ -190,13 +190,7 @@ flushBatchedUpdates = ReactPerf.measure(
190 * Mark a component as needing a rerender, adding an optional callback to a
191 * list of functions which will be executed once the rerender occurs.
192 */
193 -function enqueueUpdate(component, callback) {
194 - invariant(
195 - !callback || typeof callback === 'function',
196 - 'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
197 - '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
198 - 'isn\'t callable.'
199 - );
193 +function enqueueUpdate(component) {
194 ensureInjected();
195
196 // Various parts of our code (such as ReactCompositeComponent's
@@ -213,19 +207,11 @@ function enqueueUpdate(component, callback) {
207 );
208
209 if (!batchingStrategy.isBatchingUpdates) {
216 - batchingStrategy.batchedUpdates(enqueueUpdate, component, callback);
210 + batchingStrategy.batchedUpdates(enqueueUpdate, component);
211 return;
212 }
213
214 dirtyComponents.push(component);
221 -
222 - if (callback) {
223 - if (component._pendingCallbacks) {
224 - component._pendingCallbacks.push(callback);
225 - } else {
226 - component._pendingCallbacks = [callback];
227 - }
228 - }
215 }
216
217 /**
src/core/__tests__/ReactComponentLifeCycle-test.js
+6 -6
@@ -97,7 +97,7 @@ describe('ReactComponentLifeCycle', function() {
97 React = require('React');
98 ReactTestUtils = require('ReactTestUtils');
99 ReactCompositeComponent = require('ReactCompositeComponent');
100 - CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;
100 + CompositeComponentLifeCycle = require('ReactLifeCycle');
101
102 ReactInstanceMap = require('ReactInstanceMap');
103
@@ -403,11 +403,11 @@ describe('ReactComponentLifeCycle', function() {
403 expect(function() {
404 instance = ReactTestUtils.renderIntoDocument(instance);
405 }).toThrow(
406 - 'Invariant Violation: replaceProps(...): You called `setProps` or ' +
407 - '`replaceProps` on a component with a parent. This is an anti-pattern ' +
408 - 'since props will get reactively updated when rendered. Instead, ' +
409 - 'change the owner\'s `render` method to pass the correct value as ' +
410 - 'props to the component where it is created.'
406 + 'Invariant Violation: setProps(...): You called `setProps` on a ' +
407 + 'component with a parent. This is an anti-pattern since props will get ' +
408 + 'reactively updated when rendered. Instead, change the owner\'s ' +
409 + '`render` method to pass the correct value as props to the component ' +
410 + 'where it is created.'
411 );
412 });
413
src/core/__tests__/ReactCompositeComponent-test.js
+8 -8
@@ -294,9 +294,9 @@ describe('ReactCompositeComponent', function() {
294 expect(function() {
295 instance.forceUpdate();
296 }).toThrow(
297 - 'Invariant Violation: forceUpdate(...): Can only force an update on ' +
298 - 'mounted or mounting components. This usually means you called ' +
299 - 'forceUpdate() on an unmounted component.'
297 + 'Invariant Violation: forceUpdate(...): Can only update a mounted or ' +
298 + 'mounting component. This usually means you called forceUpdate() on ' +
299 + 'an unmounted component.'
300 );
301 });
302
@@ -406,11 +406,11 @@ describe('ReactCompositeComponent', function() {
406 expect(function() {
407 innerInstance.setProps({value: 1});
408 }).toThrow(
409 - 'Invariant Violation: replaceProps(...): You called `setProps` or ' +
410 - '`replaceProps` on a component with a parent. This is an anti-pattern ' +
411 - 'since props will get reactively updated when rendered. Instead, ' +
412 - 'change the owner\'s `render` method to pass the correct value as ' +
413 - 'props to the component where it is created.'
409 + 'Invariant Violation: setProps(...): You called `setProps` on a ' +
410 + 'component with a parent. This is an anti-pattern since props will get ' +
411 + 'reactively updated when rendered. Instead, change the owner\'s ' +
412 + '`render` method to pass the correct value as props to the component ' +
413 + 'where it is created.'
414 );
415 });
416
src/core/__tests__/ReactCompositeComponentState-test.js
+1 -1
@@ -154,7 +154,7 @@ describe('ReactCompositeComponent-state', function() {
154 [ 'render', 'orange', null ],
155 [ 'componentDidMount-start', 'orange', null ],
156 // setState-sunrise and setState-orange should be called here,
157 - // after the bug in #
157 + // after the bug in #1740
158 // componentDidMount() called setState({color:'yellow'}), currently this
159 // occurs inline.
160 // In a future where setState() is async, this test result will change.
src/modern/class/ReactComponentBase.js
+9 -20
@@ -11,7 +11,7 @@
11
12 'use strict';
13
14 -var ReactInstanceMap = require('ReactInstanceMap');
14 +var ReactUpdateQueue = require('ReactUpdateQueue');
15
16 var invariant = require('invariant');
17 var warning = require('warning');
@@ -53,17 +53,10 @@ ReactComponentBase.prototype.setState = function(partialState, callback) {
53 'instead, use forceUpdate().'
54 );
55 }
56 -
57 - var internalInstance = ReactInstanceMap.get(this);
58 - invariant(
59 - internalInstance,
60 - 'setState(...): Can only update a mounted or mounting component. ' +
61 - 'This usually means you called setState() on an unmounted ' +
62 - 'component.'
63 - );
64 - internalInstance.setState(
65 - partialState, callback
66 - );
56 + ReactUpdateQueue.enqueueSetState(this, partialState);
57 + if (callback) {
58 + ReactUpdateQueue.enqueueCallback(this, callback);
59 + }
60 };
61
62 /**
@@ -81,14 +74,10 @@ ReactComponentBase.prototype.setState = function(partialState, callback) {
74 * @protected
75 */
76 ReactComponentBase.prototype.forceUpdate = function(callback) {
84 - var internalInstance = ReactInstanceMap.get(this);
85 - invariant(
86 - internalInstance,
87 - 'forceUpdate(...): Can only force an update on mounted or mounting ' +
88 - 'components. This usually means you called forceUpdate() on an ' +
89 - 'unmounted component.'
90 - );
91 - internalInstance.forceUpdate(callback);
77 + ReactUpdateQueue.enqueueForceUpdate(this);
78 + if (callback) {
79 + ReactUpdateQueue.enqueueCallback(this, callback);
80 + }
81 };
82
83 /**