Skip to content

Commit d3ee9c6

Browse files
committed
New effect type: Callback
This solves the problem I had with enqueueSetState and enqueueCallback being separate.
1 parent f52e512 commit d3ee9c6

13 files changed

+168
-115
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ src/renderers/art/__tests__/ReactART-test.js
8787
* resolves refs before componentDidMount
8888
* resolves refs before componentDidUpdate
8989

90+
src/renderers/dom/__tests__/ReactDOMProduction-test.js
91+
* should throw with an error code in production
92+
9093
src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
9194
* should set style attribute when styles exist
9295
* should warn when using hyphenated style names
@@ -368,7 +371,6 @@ src/renderers/dom/stack/client/__tests__/ReactMount-test.js
368371
* should account for escaping on a checksum mismatch
369372
* should warn if render removes React-rendered children
370373
* should warn if the unmounted node was rendered by another copy of React
371-
* passes the correct callback context
372374
* tracks root instances
373375
* marks top-level mounts
374376

@@ -607,8 +609,6 @@ src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js
607609
* throws in setState if the update callback is not a function
608610
* throws in replaceState if the update callback is not a function
609611
* throws in forceUpdate if the update callback is not a function
610-
* does not update one component twice in a batch (#2410)
611-
* unstable_batchedUpdates should return value from a callback
612612

613613
src/renderers/shared/stack/reconciler/__tests__/refs-test.js
614614
* Should increase refs with an increase in divs

scripts/fiber/tests-passing.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,6 @@ src/renderers/dom/__tests__/ReactDOMProduction-test.js
438438
* should use prod React
439439
* should handle a simple flow
440440
* should call lifecycle methods
441-
* should throw with an error code in production
442441

443442
src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
444443
* should render strings as children
@@ -692,6 +691,7 @@ src/renderers/dom/stack/client/__tests__/ReactMount-test.js
692691
* should warn if mounting into dirty rendered markup
693692
* should not warn if mounting into non-empty node
694693
* should warn when mounting into document.body
694+
* passes the correct callback context
695695

696696
src/renderers/dom/stack/client/__tests__/ReactMountDestruction-test.js
697697
* should destroy a react root upon request
@@ -788,6 +788,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
788788
* calls componentWill* twice if an update render is aborted
789789
* does not call componentWillReceiveProps for state-only updates
790790
* 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
791793

792794
src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
793795
* handles isMounted even when the initial render is deferred
@@ -815,6 +817,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
815817
* can defer side-effects and reuse them later - complex
816818
* deprioritizes setStates that happens within a deprioritized tree
817819
* calls callback after update is flushed
820+
* calls setState callback even if component bails out
818821
* calls componentWillUnmount after a deletion, even if nested
819822
* calls componentDidMount/Update after insertion/update
820823
* invokes ref callbacks after insertion/update/unmount
@@ -1106,7 +1109,9 @@ src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js
11061109
* should not reconcile children passed via props
11071110
* should queue nested updates
11081111
* calls componentWillReceiveProps setState callback properly
1112+
* does not update one component twice in a batch (#2410)
11091113
* does not update one component twice in a batch (#6371)
1114+
* unstable_batchedUpdates should return value from a callback
11101115

11111116
src/renderers/shared/stack/reconciler/__tests__/Transaction-test.js
11121117
* should invoke closers with/only-with init returns

src/addons/transitions/__tests__/ReactTransitionGroup-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ describe('ReactTransitionGroup', () => {
9797
expect(log).toEqual(['didMount', 'willEnter', 'didEnter']);
9898

9999
log = [];
100-
instance.setState({count: 1}, function() {
101-
expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']);
102-
});
100+
instance.setState({count: 1});
103101
});
102+
103+
expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']);
104104
});
105105

106106
it('should handle enter/leave/enter/leave correctly', () => {

src/isomorphic/classic/class/ReactClass.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -728,11 +728,6 @@ var ReactClassMixin = {
728728
* type signature and the only use case for this, is to avoid that.
729729
*/
730730
replaceState: function(newState, callback) {
731-
if (this.updater.isFiberUpdater) {
732-
this.updater.enqueueReplaceState(this, newState, callback);
733-
return;
734-
}
735-
736731
this.updater.enqueueReplaceState(this, newState);
737732
if (callback) {
738733
this.updater.enqueueCallback(this, callback, 'replaceState');

src/isomorphic/modern/class/ReactComponent.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,6 @@ ReactComponent.prototype.setState = function(partialState, callback) {
6565
'setState(...): takes an object of state variables to update or a ' +
6666
'function which returns an object of state variables.'
6767
);
68-
69-
if (this.updater.isFiberUpdater) {
70-
this.updater.enqueueSetState(this, partialState, callback);
71-
return;
72-
}
73-
7468
this.updater.enqueueSetState(this, partialState);
7569
if (callback) {
7670
this.updater.enqueueCallback(this, callback, 'setState');
@@ -92,11 +86,6 @@ ReactComponent.prototype.setState = function(partialState, callback) {
9286
* @protected
9387
*/
9488
ReactComponent.prototype.forceUpdate = function(callback) {
95-
if (this.updater.isFiberUpdater) {
96-
this.updater.enqueueForceUpdate(this, callback);
97-
return;
98-
}
99-
10089
this.updater.enqueueForceUpdate(this);
10190
if (callback) {
10291
this.updater.enqueueCallback(this, callback, 'forceUpdate');

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var ReactFiberClassComponent = require('ReactFiberClassComponent');
4848

4949
module.exports = function<T, P, I, TI, C>(
5050
config : HostConfig<T, P, I, TI, C>,
51-
scheduleUpdate : (fiber: Fiber, priorityLevel : ?PriorityLevel) => void
51+
scheduleUpdate : (fiber: Fiber) => void
5252
) {
5353

5454
const {

src/renderers/shared/fiber/ReactFiberClassComponent.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var { isMounted } = require('ReactFiberTreeReflection');
2626
var ReactInstanceMap = require('ReactInstanceMap');
2727
var shallowEqual = require('shallowEqual');
2828

29-
module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?PriorityLevel) => void) {
29+
module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
3030

3131
function scheduleUpdateQueue(fiber: Fiber, updateQueue: UpdateQueue) {
3232
fiber.updateQueue = updateQueue;
@@ -41,35 +41,33 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
4141
// Class component state updater
4242
const updater = {
4343
isMounted,
44-
enqueueSetState(instance, partialState, callback) {
44+
enqueueSetState(instance, partialState) {
4545
const fiber = ReactInstanceMap.get(instance);
4646
const updateQueue = fiber.updateQueue ?
4747
addToQueue(fiber.updateQueue, partialState) :
4848
createUpdateQueue(partialState);
49-
if (callback) {
50-
addCallbackToQueue(updateQueue, callback);
51-
}
5249
scheduleUpdateQueue(fiber, updateQueue);
5350
},
54-
enqueueReplaceState(instance, state, callback) {
51+
enqueueReplaceState(instance, state) {
5552
const fiber = ReactInstanceMap.get(instance);
5653
const updateQueue = createUpdateQueue(state);
5754
updateQueue.isReplace = true;
58-
if (callback) {
59-
addCallbackToQueue(updateQueue, callback);
60-
}
6155
scheduleUpdateQueue(fiber, updateQueue);
6256
},
63-
enqueueForceUpdate(instance, callback) {
57+
enqueueForceUpdate(instance) {
6458
const fiber = ReactInstanceMap.get(instance);
6559
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
6660
updateQueue.isForced = true;
67-
if (callback) {
68-
addCallbackToQueue(updateQueue, callback);
69-
}
7061
scheduleUpdateQueue(fiber, updateQueue);
7162
},
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+
},
7371
};
7472

7573
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState) {
@@ -210,11 +208,21 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
210208
// TODO: Previous state can be null.
211209
let newState;
212210
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+
}
214216
} else {
215217
newState = previousState;
216218
}
217219

220+
if (oldProps === newProps &&
221+
previousState === newState &&
222+
updateQueue && !updateQueue.isForced) {
223+
return false;
224+
}
225+
218226
if (!checkShouldComponentUpdate(
219227
workInProgress,
220228
oldProps,

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ var { callCallbacks } = require('ReactFiberUpdateQueue');
2929

3030
var {
3131
Placement,
32-
PlacementAndUpdate,
32+
Update,
33+
Callback,
3334
} = require('ReactTypeOfSideEffect');
3435

3536
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>) {
102103
// If it is not host node and, we might have a host node inside it.
103104
// Try to search down until we find one.
104105
// TODO: For coroutines, this will have to search the stateNode.
105-
if (node.effectTag === Placement ||
106-
node.effectTag === PlacementAndUpdate) {
106+
if (node.effectTag & Placement) {
107107
// If we don't have a child, try the siblings instead.
108108
continue siblings;
109109
}
@@ -114,8 +114,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
114114
}
115115
}
116116
// 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)) {
119118
// Found it!
120119
return node.stateNode;
121120
}
@@ -329,41 +328,41 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
329328
case ClassComponent: {
330329
const instance = finishedWork.stateNode;
331330
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+
}
341342
}
343+
attachRef(current, finishedWork, instance);
342344
}
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.
347346
if (finishedWork.alternate) {
348347
finishedWork.alternate.updateQueue = null;
349348
}
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+
}
354354
}
355-
attachRef(current, finishedWork, instance);
356355
if (error) {
357356
return trapError(finishedWork, error);
358357
}
359358
return null;
360359
}
361360
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);
367366
}
368367
}
369368
case HostComponent: {

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var {
3434
} = ReactTypeOfWork;
3535
var {
3636
Update,
37+
Callback,
3738
} = ReactTypeOfSideEffect;
3839

3940
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>) {
4849
workInProgress.effectTag |= Update;
4950
}
5051

52+
function markCallback(workInProgress : Fiber) {
53+
// Tag the fiber with a callback effect.
54+
workInProgress.effectTag |= Callback;
55+
}
56+
5157
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
5258
// If we have a single result, we just pass that through as the output to
5359
// 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>) {
124130
// Also need to transfer the props, because pendingProps will be null
125131
// in the case of an update
126132
const { state, props } = workInProgress.stateNode;
133+
const updateQueue = workInProgress.updateQueue;
127134
workInProgress.memoizedState = state;
128135
workInProgress.memoizedProps = props;
129-
// Transfer update queue to callbackList field so callbacks can be
130-
// called during commit phase.
131-
workInProgress.callbackList = workInProgress.updateQueue;
132136
if (current) {
133137
if (current.memoizedProps !== workInProgress.memoizedProps ||
134-
current.memoizedState !== workInProgress.memoizedState) {
138+
current.memoizedState !== workInProgress.memoizedState ||
139+
updateQueue && updateQueue.isForced) {
135140
markUpdate(workInProgress);
136141
}
137142
} else {
138143
markUpdate(workInProgress);
139144
}
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+
}
140151
return null;
141152
case HostContainer:
142153
transferOutput(workInProgress.child, workInProgress);

0 commit comments

Comments
 (0)