@samitouri / QOS-React / commits / c3ab541e0f

New effect type: Callback

This solves the problem I had with enqueueSetState and enqueueCallback being separate.

Andrew Clark committed Nov 1, 2016 at 17:41 UTC c3ab541e0f73056ca1b442876da5ede24197ed28
13 files changed +168 -115
scripts/fiber/tests-failing.txt
+3 -3
@@ -87,6 +87,9 @@ src/renderers/art/__tests__/ReactART-test.js
87 * resolves refs before componentDidMount
88 * resolves refs before componentDidUpdate
89
90 +src/renderers/dom/__tests__/ReactDOMProduction-test.js
91 +* should throw with an error code in production
92 +
93 src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
94 * should set style attribute when styles exist
95 * should warn when using hyphenated style names
@@ -369,7 +372,6 @@ src/renderers/dom/stack/client/__tests__/ReactMount-test.js
372 * should account for escaping on a checksum mismatch
373 * should warn if render removes React-rendered children
374 * should warn if the unmounted node was rendered by another copy of React
372 -* passes the correct callback context
375 * tracks root instances
376 * marks top-level mounts
377
@@ -608,8 +610,6 @@ src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js
610 * throws in setState if the update callback is not a function
611 * throws in replaceState if the update callback is not a function
612 * throws in forceUpdate if the update callback is not a function
611 -* does not update one component twice in a batch (#2410)
612 -* unstable_batchedUpdates should return value from a callback
613
614 src/renderers/shared/stack/reconciler/__tests__/refs-test.js
615 * Should increase refs with an increase in divs
scripts/fiber/tests-passing.txt
+6 -1
@@ -438,7 +438,6 @@ src/renderers/dom/__tests__/ReactDOMProduction-test.js
438 * should use prod React
439 * should handle a simple flow
440 * should call lifecycle methods
441 -* should throw with an error code in production
441
442 src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
443 * should render strings as children
@@ -692,6 +691,7 @@ src/renderers/dom/stack/client/__tests__/ReactMount-test.js
691 * should warn if mounting into dirty rendered markup
692 * should not warn if mounting into non-empty node
693 * should warn when mounting into document.body
694 +* passes the correct callback context
695
696 src/renderers/dom/stack/client/__tests__/ReactMountDestruction-test.js
697 * should destroy a react root upon request
@@ -788,6 +788,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
788 * calls componentWill* twice if an update render is aborted
789 * does not call componentWillReceiveProps for state-only updates
790 * skips will/DidUpdate when bailing unless an update was already in progress
791 +* performs batched updates at the end of the batch
792 +* can nest batchedUpdates
793
794 src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
795 * handles isMounted even when the initial render is deferred
@@ -833,6 +835,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
835 * can defer side-effects and reuse them later - complex
836 * deprioritizes setStates that happens within a deprioritized tree
837 * calls callback after update is flushed
838 +* calls setState callback even if component bails out
839 * calls componentWillUnmount after a deletion, even if nested
840 * calls componentDidMount/Update after insertion/update
841 * invokes ref callbacks after insertion/update/unmount
@@ -1124,7 +1127,9 @@ src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js
1127 * should not reconcile children passed via props
1128 * should queue nested updates
1129 * calls componentWillReceiveProps setState callback properly
1130 +* does not update one component twice in a batch (#2410)
1131 * does not update one component twice in a batch (#6371)
1132 +* unstable_batchedUpdates should return value from a callback
1133
1134 src/renderers/shared/stack/reconciler/__tests__/Transaction-test.js
1135 * should invoke closers with/only-with init returns
src/addons/transitions/__tests__/ReactTransitionGroup-test.js
+3 -3
@@ -97,10 +97,10 @@ describe('ReactTransitionGroup', () => {
97 expect(log).toEqual(['didMount', 'willEnter', 'didEnter']);
98
99 log = [];
100 - instance.setState({count: 1}, function() {
101 - expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']);
102 - });
100 + instance.setState({count: 1});
101 });
102 +
103 + expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']);
104 });
105
106 it('should handle enter/leave/enter/leave correctly', () => {
src/isomorphic/classic/class/ReactClass.js
-5
@@ -728,11 +728,6 @@ var ReactClassMixin = {
728 * type signature and the only use case for this, is to avoid that.
729 */
730 replaceState: function(newState, callback) {
731 - if (this.updater.isFiberUpdater) {
732 - this.updater.enqueueReplaceState(this, newState, callback);
733 - return;
734 - }
735 -
731 this.updater.enqueueReplaceState(this, newState);
732 if (callback) {
733 this.updater.enqueueCallback(this, callback, 'replaceState');
src/isomorphic/modern/class/ReactComponent.js
-11
@@ -65,12 +65,6 @@ ReactComponent.prototype.setState = function(partialState, callback) {
65 'setState(...): takes an object of state variables to update or a ' +
66 'function which returns an object of state variables.'
67 );
68 -
69 - if (this.updater.isFiberUpdater) {
70 - this.updater.enqueueSetState(this, partialState, callback);
71 - return;
72 - }
73 -
68 this.updater.enqueueSetState(this, partialState);
69 if (callback) {
70 this.updater.enqueueCallback(this, callback, 'setState');
@@ -92,11 +86,6 @@ ReactComponent.prototype.setState = function(partialState, callback) {
86 * @protected
87 */
88 ReactComponent.prototype.forceUpdate = function(callback) {
95 - if (this.updater.isFiberUpdater) {
96 - this.updater.enqueueForceUpdate(this, callback);
97 - return;
98 - }
99 -
89 this.updater.enqueueForceUpdate(this);
90 if (callback) {
91 this.updater.enqueueCallback(this, callback, 'forceUpdate');
src/renderers/shared/fiber/ReactFiberBeginWork.js
+1 -1
@@ -48,7 +48,7 @@ var ReactFiberClassComponent = require('ReactFiberClassComponent');
48
49 module.exports = function<T, P, I, TI, C>(
50 config : HostConfig<T, P, I, TI, C>,
51 - scheduleUpdate : (fiber: Fiber, priorityLevel : ?PriorityLevel) => void
51 + scheduleUpdate : (fiber: Fiber) => void
52 ) {
53
54 const {
src/renderers/shared/fiber/ReactFiberClassComponent.js
+23 -15
@@ -26,7 +26,7 @@ var { isMounted } = require('ReactFiberTreeReflection');
26 var ReactInstanceMap = require('ReactInstanceMap');
27 var shallowEqual = require('shallowEqual');
28
29 -module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?PriorityLevel) => void) {
29 +module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
30
31 function scheduleUpdateQueue(fiber: Fiber, updateQueue: UpdateQueue) {
32 fiber.updateQueue = updateQueue;
@@ -41,35 +41,33 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
41 // Class component state updater
42 const updater = {
43 isMounted,
44 - enqueueSetState(instance, partialState, callback) {
44 + enqueueSetState(instance, partialState) {
45 const fiber = ReactInstanceMap.get(instance);
46 const updateQueue = fiber.updateQueue ?
47 addToQueue(fiber.updateQueue, partialState) :
48 createUpdateQueue(partialState);
49 - if (callback) {
50 - addCallbackToQueue(updateQueue, callback);
51 - }
49 scheduleUpdateQueue(fiber, updateQueue);
50 },
54 - enqueueReplaceState(instance, state, callback) {
51 + enqueueReplaceState(instance, state) {
52 const fiber = ReactInstanceMap.get(instance);
53 const updateQueue = createUpdateQueue(state);
54 updateQueue.isReplace = true;
58 - if (callback) {
59 - addCallbackToQueue(updateQueue, callback);
60 - }
55 scheduleUpdateQueue(fiber, updateQueue);
56 },
63 - enqueueForceUpdate(instance, callback) {
57 + enqueueForceUpdate(instance) {
58 const fiber = ReactInstanceMap.get(instance);
59 const updateQueue = fiber.updateQueue || createUpdateQueue(null);
60 updateQueue.isForced = true;
67 - if (callback) {
68 - addCallbackToQueue(updateQueue, callback);
69 - }
61 scheduleUpdateQueue(fiber, updateQueue);
62 },
72 - isFiberUpdater: true,
63 + enqueueCallback(instance, callback) {
64 + const fiber = ReactInstanceMap.get(instance);
65 + let updateQueue = fiber.updateQueue ?
66 + fiber.updateQueue :
67 + createUpdateQueue(null);
68 + addCallbackToQueue(updateQueue, callback);
69 + scheduleUpdateQueue(fiber, updateQueue);
70 + },
71 };
72
73 function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState) {
@@ -210,11 +208,21 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
208 // TODO: Previous state can be null.
209 let newState;
210 if (updateQueue) {
213 - newState = mergeUpdateQueue(updateQueue, instance, previousState, newProps);
211 + if (!updateQueue.hasUpdate) {
212 + newState = previousState;
213 + } else {
214 + newState = mergeUpdateQueue(updateQueue, instance, previousState, newProps);
215 + }
216 } else {
217 newState = previousState;
218 }
219
220 + if (oldProps === newProps &&
221 + previousState === newState &&
222 + updateQueue && !updateQueue.isForced) {
223 + return false;
224 + }
225 +
226 if (!checkShouldComponentUpdate(
227 workInProgress,
228 oldProps,
src/renderers/shared/fiber/ReactFiberCommitWork.js
+27 -28
@@ -29,7 +29,8 @@ var { callCallbacks } = require('ReactFiberUpdateQueue');
29
30 var {
31 Placement,
32 - PlacementAndUpdate,
32 + Update,
33 + Callback,
34 } = require('ReactTypeOfSideEffect');
35
36 module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
@@ -102,8 +103,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
103 // If it is not host node and, we might have a host node inside it.
104 // Try to search down until we find one.
105 // TODO: For coroutines, this will have to search the stateNode.
105 - if (node.effectTag === Placement ||
106 - node.effectTag === PlacementAndUpdate) {
106 + if (node.effectTag & Placement) {
107 // If we don't have a child, try the siblings instead.
108 continue siblings;
109 }
@@ -114,8 +114,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
114 }
115 }
116 // Check if this host node is stable or about to be placed.
117 - if (node.effectTag !== Placement &&
118 - node.effectTag !== PlacementAndUpdate) {
117 + if (!(node.effectTag & Placement)) {
118 // Found it!
119 return node.stateNode;
120 }
@@ -329,41 +328,41 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
328 case ClassComponent: {
329 const instance = finishedWork.stateNode;
330 let error = null;
332 - if (!current) {
333 - if (typeof instance.componentDidMount === 'function') {
334 - error = tryCallComponentDidMount(instance);
335 - }
336 - } else {
337 - if (typeof instance.componentDidUpdate === 'function') {
338 - const prevProps = current.memoizedProps;
339 - const prevState = current.memoizedState;
340 - error = tryCallComponentDidUpdate(instance, prevProps, prevState);
331 + if (finishedWork.effectTag & Update) {
332 + if (!current) {
333 + if (typeof instance.componentDidMount === 'function') {
334 + error = tryCallComponentDidMount(instance);
335 + }
336 + } else {
337 + if (typeof instance.componentDidUpdate === 'function') {
338 + const prevProps = current.memoizedProps;
339 + const prevState = current.memoizedState;
340 + error = tryCallComponentDidUpdate(instance, prevProps, prevState);
341 + }
342 }
343 + attachRef(current, finishedWork, instance);
344 }
343 - // Clear updates from current fiber. This must go before the callbacks
344 - // are reset, in case an update is triggered from inside a callback. Is
345 - // this safe? Relies on the assumption that work is only committed if
346 - // the update queue is empty.
345 + // Clear updates from current fiber.
346 if (finishedWork.alternate) {
347 finishedWork.alternate.updateQueue = null;
348 }
350 - if (finishedWork.callbackList) {
351 - const { callbackList } = finishedWork;
352 - finishedWork.callbackList = null;
353 - callCallbacks(callbackList, instance);
349 + if (finishedWork.effectTag & Callback) {
350 + if (finishedWork.callbackList) {
351 + callCallbacks(finishedWork.callbackList, instance);
352 + finishedWork.callbackList = null;
353 + }
354 }
355 - attachRef(current, finishedWork, instance);
355 if (error) {
356 return trapError(finishedWork, error);
357 }
358 return null;
359 }
360 case HostContainer: {
362 - const instance = finishedWork.stateNode;
363 - if (instance.callbackList) {
364 - const { callbackList } = instance;
365 - instance.callbackList = null;
366 - callCallbacks(callbackList, instance.current.child.stateNode);
361 + const rootFiber = finishedWork.stateNode;
362 + if (rootFiber.callbackList) {
363 + const { callbackList } = rootFiber;
364 + rootFiber.callbackList = null;
365 + callCallbacks(callbackList, rootFiber.current.child.stateNode);
366 }
367 }
368 case HostComponent: {
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+15 -4
@@ -34,6 +34,7 @@ var {
34 } = ReactTypeOfWork;
35 var {
36 Update,
37 + Callback,
38 } = ReactTypeOfSideEffect;
39
40 module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
@@ -48,6 +49,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
49 workInProgress.effectTag |= Update;
50 }
51
52 + function markCallback(workInProgress : Fiber) {
53 + // Tag the fiber with a callback effect.
54 + workInProgress.effectTag |= Callback;
55 + }
56 +
57 function transferOutput(child : ?Fiber, returnFiber : Fiber) {
58 // If we have a single result, we just pass that through as the output to
59 // avoid unnecessary traversal. When we have multiple output, we just pass
@@ -124,19 +130,24 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
130 // Also need to transfer the props, because pendingProps will be null
131 // in the case of an update
132 const { state, props } = workInProgress.stateNode;
133 + const updateQueue = workInProgress.updateQueue;
134 workInProgress.memoizedState = state;
135 workInProgress.memoizedProps = props;
129 - // Transfer update queue to callbackList field so callbacks can be
130 - // called during commit phase.
131 - workInProgress.callbackList = workInProgress.updateQueue;
136 if (current) {
137 if (current.memoizedProps !== workInProgress.memoizedProps ||
134 - current.memoizedState !== workInProgress.memoizedState) {
138 + current.memoizedState !== workInProgress.memoizedState ||
139 + updateQueue && updateQueue.isForced) {
140 markUpdate(workInProgress);
141 }
142 } else {
143 markUpdate(workInProgress);
144 }
145 + if (updateQueue && updateQueue.hasCallback) {
146 + // Transfer update queue to callbackList field so callbacks can be
147 + // called during commit phase.
148 + workInProgress.callbackList = updateQueue;
149 + markCallback(workInProgress);
150 + }
151 return null;
152 case HostContainer:
153 transferOutput(workInProgress.child, workInProgress);
src/renderers/shared/fiber/ReactFiberScheduler.js
+37 -36
@@ -39,6 +39,11 @@ var {
39 Update,
40 PlacementAndUpdate,
41 Deletion,
42 + Callback,
43 + PlacementAndCallback,
44 + UpdateAndCallback,
45 + PlacementAndUpdateAndCallback,
46 + DeletionAndCallback,
47 } = require('ReactTypeOfSideEffect');
48
49 var {
@@ -52,9 +57,6 @@ if (__DEV__) {
57 var timeHeuristicForUnitOfWork = 1;
58
59 module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
55 - // Use a closure to circumvent the circular dependency between the scheduler
56 - // and ReactFiberBeginWork. Don't know if there's a better way to do this.
57 -
60 const { beginWork } = ReactFiberBeginWork(config, scheduleUpdate);
61 const { completeWork } = ReactFiberCompleteWork(config);
62 const { commitInsertion, commitDeletion, commitWork, commitLifeCycles } =
@@ -139,28 +141,34 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
141 let effectfulFiber = finishedWork.firstEffect;
142 while (effectfulFiber) {
143 switch (effectfulFiber.effectTag) {
142 - case Placement: {
144 + case Placement:
145 + case PlacementAndCallback: {
146 commitInsertion(effectfulFiber);
144 - // Clear the effect tag so that we know that this is inserted, before
147 + // Clear the "placement" from effect tag so that we know that this is inserted, before
148 // any life-cycles like componentDidMount gets called.
149 effectfulFiber.effectTag = NoEffect;
150 break;
151 }
149 - case PlacementAndUpdate: {
152 + case PlacementAndUpdate:
153 + case PlacementAndUpdateAndCallback: {
154 + // Placement
155 commitInsertion(effectfulFiber);
151 - const current = effectfulFiber.alternate;
152 - commitWork(current, effectfulFiber);
156 // Clear the "placement" from effect tag so that we know that this is inserted, before
157 // any life-cycles like componentDidMount gets called.
158 effectfulFiber.effectTag = Update;
159 +
160 + // Update
161 + const current = effectfulFiber.alternate;
162 + commitWork(current, effectfulFiber);
163 break;
164 }
158 - case Update: {
165 + case Update:
166 + case UpdateAndCallback:
167 const current = effectfulFiber.alternate;
168 commitWork(current, effectfulFiber);
169 break;
162 - }
163 - case Deletion: {
170 + case Deletion:
171 + case DeletionAndCallback:
172 // Deletion might cause an error in componentWillUnmount().
173 // We will continue nevertheless and handle those later on.
174 const trappedErrors = commitDeletion(effectfulFiber);
@@ -176,8 +184,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
184 }
185 }
186 break;
179 - }
187 }
188 +
189 effectfulFiber = effectfulFiber.nextEffect;
190 }
191
@@ -186,8 +194,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
194 // already been invoked.
195 effectfulFiber = finishedWork.firstEffect;
196 while (effectfulFiber) {
189 - if (effectfulFiber.effectTag === Update ||
190 - effectfulFiber.effectTag === PlacementAndUpdate) {
197 + if (effectfulFiber.effectTag & (Update | Callback)) {
198 const current = effectfulFiber.alternate;
199 const trappedError = commitLifeCycles(current, effectfulFiber);
200 if (trappedError) {
@@ -540,22 +547,22 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
547 }
548 }
549
543 - function performAndHandleErrors(priorityLevel : PriorityLevel, deadline : ?Deadline) {
550 + function performAndHandleErrors(priorityLevel : PriorityLevel, deadline : null | Deadline) {
551 // The exact priority level doesn't matter, so long as it's in range of the
552 // work (sync, animation, deferred) being performed.
553 try {
554 if (priorityLevel === SynchronousPriority) {
555 performSynchronousWorkUnsafe();
549 - }
550 - if (priorityLevel > AnimationPriority) {
556 + } else if (priorityLevel > AnimationPriority) {
557 if (!deadline) {
558 throw new Error('No deadline');
559 } else {
560 performDeferredWorkUnsafe(deadline);
561 }
562 return;
563 + } else {
564 + performAnimationWorkUnsafe();
565 }
558 - performAnimationWorkUnsafe();
566 } catch (error) {
567 const failedUnitOfWork = nextUnitOfWork;
568 // Reset because it points to the error boundary:
@@ -639,31 +646,25 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
646 }
647 }
648
642 - function scheduleWork(root : FiberRoot, priorityLevel : ?PriorityLevel) {
643 - if (priorityLevel == null) {
644 - priorityLevel = priorityContext;
645 - }
646 -
647 - if (priorityLevel === SynchronousPriority) {
648 - scheduleSynchronousWork(root);
649 - }
649 + function scheduleWork(root : FiberRoot) {
650 + scheduleWorkAtPriority(root, priorityContext);
651 + }
652
653 + function scheduleWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) {
654 if (priorityLevel === NoWork) {
655 return;
653 - }
654 - if (priorityLevel > AnimationPriority) {
656 + } else if (priorityLevel === SynchronousPriority) {
657 + scheduleSynchronousWork(root);
658 + } else if (priorityLevel <= AnimationPriority) {
659 + scheduleAnimationWork(root, priorityLevel);
660 + } else {
661 scheduleDeferredWork(root, priorityLevel);
662 return;
663 }
658 - scheduleAnimationWork(root, priorityLevel);
664 }
665
661 - function scheduleUpdate(fiber: Fiber, priorityLevel : ?PriorityLevel): void {
662 - // Use priority context if no priority is provided
663 - if (priorityLevel == null) {
664 - priorityLevel = priorityContext;
665 - }
666 -
666 + function scheduleUpdate(fiber : Fiber) {
667 + const priorityLevel = priorityContext;
668 while (true) {
669 if (fiber.pendingWorkPriority === NoWork ||
670 fiber.pendingWorkPriority >= priorityLevel) {
@@ -678,7 +679,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
679 if (!fiber.return) {
680 if (fiber.tag === HostContainer) {
681 const root : FiberRoot = (fiber.stateNode : any);
681 - scheduleWork(root, priorityLevel);
682 + scheduleWorkAtPriority(root, priorityLevel);
683 return;
684 } else {
685 throw new Error('Invalid root');
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+13 -2
@@ -22,6 +22,8 @@ type UpdateQueueNode = {
22 export type UpdateQueue = UpdateQueueNode & {
23 isReplace: boolean,
24 isForced: boolean,
25 + hasUpdate: boolean,
26 + hasCallback: boolean,
27 tail: UpdateQueueNode
28 };
29
@@ -33,6 +35,8 @@ exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue {
35 next: null,
36 isReplace: false,
37 isForced: false,
38 + hasUpdate: partialState != null,
39 + hasCallback: false,
40 tail: (null : any),
41 };
42 queue.tail = queue;
@@ -48,6 +52,7 @@ function addToQueue(queue : UpdateQueue, partialState : mixed) : UpdateQueue {
52 };
53 queue.tail.next = node;
54 queue.tail = node;
55 + queue.hasUpdate = queue.hasUpdate || (partialState == null);
56 return queue;
57 }
58
@@ -59,15 +64,21 @@ exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) :
64 addToQueue(queue, null);
65 }
66 queue.tail.callback = callback;
67 + queue.hasCallback = true;
68 return queue;
69 };
70
71 exports.callCallbacks = function(queue : UpdateQueue, context : any) {
72 let node : ?UpdateQueueNode = queue;
73 while (node) {
68 - if (node.callback && !node.callbackWasCalled) {
74 + const callback = node.callback;
75 + if (callback && !node.callbackWasCalled) {
76 node.callbackWasCalled = true;
70 - node.callback.call(context);
77 + if (typeof context !== 'undefined') {
78 + callback.call(context);
79 + } else {
80 + callback();
81 + }
82 }
83 node = node.next;
84 }
src/renderers/shared/fiber/ReactTypeOfSideEffect.js
+11 -6
@@ -12,12 +12,17 @@
12
13 'use strict';
14
15 -export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4;
15 +export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12;
16
17 module.exports = {
18 - NoEffect: 0,
19 - Placement: 1,
20 - Update: 2,
21 - PlacementAndUpdate: 3,
22 - Deletion: 4,
18 + NoEffect: 0, // 0b0000
19 + Placement: 1, // 0b0001
20 + Update: 2, // 0b0010
21 + PlacementAndUpdate: 3, // 0b0011
22 + Deletion: 4, // 0b0100
23 + Callback: 8, // 0b1000
24 + PlacementAndCallback: 9, // 0b1001
25 + UpdateAndCallback: 10, // 0b1010
26 + PlacementAndUpdateAndCallback: 11, // 0b1011
27 + DeletionAndCallback: 12, // 0b1100
28 };
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+29
@@ -860,6 +860,35 @@ describe('ReactIncrementalSideEffects', () => {
860 expect(called).toBe(true);
861 });
862
863 + it('calls setState callback even if component bails out', () => {
864 + let instance;
865 + class Foo extends React.Component {
866 + constructor() {
867 + super();
868 + instance = this;
869 + this.state = { text: 'foo' };
870 + }
871 + shouldComponentUpdate(nextProps, nextState) {
872 + return this.state.text !== nextState.text;
873 + }
874 + render() {
875 + return <span prop={this.state.text} />;
876 + }
877 + }
878 +
879 + ReactNoop.render(<Foo />);
880 + ReactNoop.flush();
881 + expect(ReactNoop.getChildren()).toEqual([
882 + span('foo'),
883 + ]);
884 + let called = false;
885 + instance.setState({}, () => {
886 + called = true;
887 + });
888 + ReactNoop.flush();
889 + expect(called).toBe(true);
890 + });
891 +
892 // TODO: Test that callbacks are not lost if an update is preempted.
893
894 it('calls componentWillUnmount after a deletion, even if nested', () => {