Remove unmountContainer
Instead, renderers should pass null to updateContainer.
Andrew Clark committed
Dec 16, 2016 at 18:31 UTC
da4ace152b5345bed7bb0e1fca33c8c47aeae70a
10 files changed
+100
-46
scripts/fiber/tests-failing.txt
-1
@@ -90,7 +90,6 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
90
* throws in setState if the update callback is not a function
91
* throws in replaceState if the update callback is not a function
92
* throws in forceUpdate if the update callback is not a function
93
-* unmounts and remounts a root in the same batch
93
94
src/renderers/shared/shared/__tests__/refs-test.js
95
* Should increase refs with an increase in divs
scripts/fiber/tests-passing.txt
+1
@@ -1559,6 +1559,7 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
1559
* does not update one component twice in a batch (#2410)
1560
* does not update one component twice in a batch (#6371)
1561
* unstable_batchedUpdates should return value from a callback
1562
+* unmounts and remounts a root in the same batch
1563
1564
src/renderers/shared/shared/__tests__/refs-destruction-test.js
1565
* should remove refs when destroying the parent
src/renderers/art/ReactARTFiber.js
+5
-1
@@ -357,7 +357,11 @@ class Surface extends Component {
357
}
358
359
componentWillUnmount() {
360
- ARTRenderer.unmountContainer(this._mountNode);
360
+ ARTRenderer.updateContainer(
361
+ null,
362
+ this._mountNode,
363
+ this,
364
+ );
365
}
366
367
render() {
src/renderers/dom/fiber/ReactDOMFiber.js
+5
-9
@@ -259,7 +259,7 @@ function warnAboutUnstableUse() {
259
warned = true;
260
}
261
262
-function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
262
+function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, children : ReactNodeList, containerNode : DOMContainerElement | Document, callback: ?Function) {
263
let container : DOMContainerElement =
264
containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
265
let root;
@@ -268,9 +268,9 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
268
while (container.lastChild) {
269
container.removeChild(container.lastChild);
270
}
271
- root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
271
+ root = container._reactRootContainer = DOMRenderer.mountContainer(children, container, parentComponent, callback);
272
} else {
273
- DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
273
+ DOMRenderer.updateContainer(children, root = container._reactRootContainer, parentComponent, callback);
274
}
275
return DOMRenderer.getPublicRootInstance(root);
276
}
@@ -292,13 +292,9 @@ var ReactDOM = {
292
293
unmountComponentAtNode(container : DOMContainerElement) {
294
warnAboutUnstableUse();
295
- const root = container._reactRootContainer;
296
- if (root) {
297
- // TODO: Is it safe to reset this now or should I wait since this
298
- // unmount could be deferred?
295
+ return renderSubtreeIntoContainer(null, null, container, () => {
296
container._reactRootContainer = null;
300
- DOMRenderer.unmountContainer(root);
301
- }
297
+ });
298
},
299
300
findDOMNode: findDOMNode,
src/renderers/native/ReactNativeFiber.js
+4
-3
@@ -329,7 +329,7 @@ const NativeRenderer = ReactFiberReconciler({
329
// But creates an additional child Fiber for raw text children.
330
// No additional native views are created though.
331
// It's not clear to me which is better so I'm deferring for now.
332
- // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
332
+ // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
333
return false;
334
},
335
@@ -375,8 +375,9 @@ const ReactNative = {
375
const root = roots.get(containerTag);
376
if (root) {
377
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
378
- roots.delete(containerTag);
379
- NativeRenderer.unmountContainer(root);
378
+ NativeRenderer.updateContainer(null, root, null, () => {
379
+ roots.delete(containerTag);
380
+ });
381
}
382
},
383
src/renderers/noop/ReactNoop.js
+4
-3
@@ -198,10 +198,11 @@ var ReactNoop = {
198
199
unmountRootWithID(rootID : string) {
200
const root = roots.get(rootID);
201
- roots.delete(rootID);
202
- rootContainers.delete(rootID);
201
if (root) {
204
- NoopRenderer.unmountContainer(root);
202
+ NoopRenderer.updateContainer(null, root, null, () => {
203
+ roots.delete(rootID);
204
+ rootContainers.delete(rootID);
205
+ });
206
}
207
},
208
src/renderers/shared/fiber/ReactFiberReconciler.js
+17
-22
@@ -15,6 +15,7 @@
15
import type { Fiber } from 'ReactFiber';
16
import type { FiberRoot } from 'ReactFiberRoot';
17
import type { PriorityLevel } from 'ReactPriorityLevel';
18
+import type { ReactNodeList } from 'ReactTypes';
19
20
var {
21
findCurrentUnmaskedContext,
@@ -70,9 +71,8 @@ export type HostConfig<T, P, I, TI, C, CX> = {
71
};
72
73
export type Reconciler<C, I, TI> = {
73
- mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
74
- updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
75
- unmountContainer(container : OpaqueNode) : void,
74
+ mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
75
+ updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
76
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
77
/* eslint-disable no-undef */
78
// FIXME: ESLint complains about type parameter
@@ -98,7 +98,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
98
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
99
100
var {
101
- scheduleSetState,
101
+ scheduleTopLevelSetState,
102
scheduleUpdateCallback,
103
performWithPriority,
104
batchedUpdates,
@@ -108,12 +108,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
108
109
return {
110
111
- mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
111
+ mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
112
const context = getContextForSubtree(parentComponent);
113
const root = createFiberRoot(containerInfo, context);
114
const current = root.current;
115
116
- scheduleSetState(current, { element });
116
+ scheduleTopLevelSetState(current, { element });
117
if (callback) {
118
scheduleUpdateCallback(current, callback);
119
}
@@ -128,32 +128,27 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
128
return current;
129
},
130
131
- updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
131
+ updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
132
// TODO: If this is a nested container, this won't be the root.
133
const root : FiberRoot = (container.stateNode : any);
134
const current = root.current;
135
136
root.pendingContext = getContextForSubtree(parentComponent);
137
138
- scheduleSetState(current, { element });
138
+ scheduleTopLevelSetState(current, { element });
139
if (callback) {
140
scheduleUpdateCallback(current, callback);
141
}
142
143
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
144
- ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
145
- }
146
- },
147
-
148
- unmountContainer(container : OpaqueNode) : void {
149
- // TODO: If this is a nested container, this won't be the root.
150
- const root : FiberRoot = (container.stateNode : any);
151
- const current = root.current;
152
-
153
- scheduleSetState(current, { element: [] });
154
-
155
- if (__DEV__ && ReactFiberInstrumentation.debugTool) {
156
- ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
143
+ if (__DEV__) {
144
+ if (ReactFiberInstrumentation.debugTool) {
145
+ if (element === null) {
146
+ ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
147
+ } else {
148
+ // This is an unmount
149
+ ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
150
+ }
151
+ }
152
}
153
},
154
src/renderers/shared/fiber/ReactFiberScheduler.js
+8
-1
@@ -59,6 +59,7 @@ var {
59
addReplaceUpdate,
60
addForceUpdate,
61
addCallback,
62
+ addTopLevelUpdate,
63
} = require('ReactFiberUpdateQueue');
64
65
var {
@@ -1058,6 +1059,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1059
scheduleUpdateAtPriority(fiber, priorityContext);
1060
}
1061
1062
+ // TODO: This indirection will be removed as part of #8585
1063
+ function scheduleTopLevelSetState(fiber : Fiber, partialState : any) {
1064
+ addTopLevelUpdate(fiber, partialState, priorityContext);
1065
+ scheduleUpdateAtPriority(fiber, priorityContext);
1066
+ }
1067
+
1068
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
1069
const previousPriorityContext = priorityContext;
1070
priorityContext = priorityLevel;
@@ -1105,7 +1112,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1112
}
1113
1114
return {
1108
- scheduleSetState: scheduleSetState,
1115
+ scheduleTopLevelSetState: scheduleTopLevelSetState,
1116
scheduleUpdateCallback: scheduleUpdateCallback,
1117
performWithPriority: performWithPriority,
1118
batchedUpdates: batchedUpdates,
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+55
-6
@@ -39,6 +39,7 @@ type Update = {
39
callback: Callback | null,
40
isReplace: boolean,
41
isForced: boolean,
42
+ isTopLevelUnmount: boolean,
43
next: Update | null,
44
};
45
@@ -141,6 +142,7 @@ function cloneUpdate(update : Update) : Update {
142
callback: update.callback,
143
isReplace: update.isReplace,
144
isForced: update.isForced,
145
+ isTopLevelUnmount: update.isTopLevelUnmount,
146
next: null,
147
};
148
}
@@ -209,7 +211,9 @@ function findInsertionPosition(queue, update) : Update | null {
211
//
212
// However, if incoming update is inserted into the same position of both lists,
213
// we shouldn't make a copy.
212
-function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : void {
214
+//
215
+// If the update is cloned, it returns the cloned update.
216
+function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : Update | null {
217
const queue1 = ensureUpdateQueue(fiber);
218
const queue2 = fiber.alternate ? ensureUpdateQueue(fiber.alternate) : null;
219
@@ -244,7 +248,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
248
if (!queue2) {
249
// If there's no alternate queue, there's nothing else to do but insert.
250
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
247
- return;
251
+ return null;
252
}
253
254
// If there is an alternate queue, find the insertion position.
@@ -260,6 +264,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
264
// insert the clone into the alternate queue.
265
const update2 = cloneUpdate(update);
266
insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
267
+ return update2;
268
} else {
269
// The insertion positions are the same, so when we inserted into the first
270
// queue, it also inserted into the alternate. All we need to do is update
@@ -272,6 +277,8 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
277
queue2.last = null;
278
}
279
}
280
+
281
+ return null;
282
}
283
284
function addUpdate(
@@ -285,6 +292,7 @@ function addUpdate(
292
callback: null,
293
isReplace: false,
294
isForced: false,
295
+ isTopLevelUnmount: false,
296
next: null,
297
};
298
if (__DEV__) {
@@ -306,6 +314,7 @@ function addReplaceUpdate(
314
callback: null,
315
isReplace: true,
316
isForced: false,
317
+ isTopLevelUnmount: false,
318
next: null,
319
};
320
@@ -324,6 +333,7 @@ function addForceUpdate(fiber : Fiber, priorityLevel : PriorityLevel) : void {
333
callback: null,
334
isReplace: false,
335
isForced: true,
336
+ isTopLevelUnmount: false,
337
next: null,
338
};
339
if (__DEV__) {
@@ -342,6 +352,7 @@ function addCallback(fiber : Fiber, callback: Callback, priorityLevel : Priority
352
callback,
353
isReplace: false,
354
isForced: false,
355
+ isTopLevelUnmount: false,
356
next: null,
357
};
358
insertUpdate(fiber, update);
@@ -353,6 +364,42 @@ function getPendingPriority(queue : UpdateQueue) : PriorityLevel {
364
}
365
exports.getPendingPriority = getPendingPriority;
366
367
+function addTopLevelUpdate(
368
+ fiber : Fiber,
369
+ partialState : PartialState<any, any>,
370
+ priorityLevel : PriorityLevel
371
+) : void {
372
+ const isTopLevelUnmount = partialState === null;
373
+
374
+ const update = {
375
+ priorityLevel,
376
+ partialState,
377
+ callback: null,
378
+ isReplace: false,
379
+ isForced: false,
380
+ isTopLevelUnmount,
381
+ next: null,
382
+ };
383
+ const update2 = insertUpdate(fiber, update);
384
+
385
+ if (isTopLevelUnmount) {
386
+ // Drop all updates that are lower-priority, so that the tree is not
387
+ // remounted. We need to do this for both queues.
388
+ const queue1 = fiber.updateQueue;
389
+ const queue2 = fiber.alternate && fiber.alternate.updateQueue;
390
+
391
+ if (queue1 && update.next) {
392
+ update.next = null;
393
+ queue1.last = update;
394
+ }
395
+ if (queue2 && update2 && update2.next) {
396
+ update2.next = null;
397
+ queue2.last = update;
398
+ }
399
+ }
400
+}
401
+exports.addTopLevelUpdate = addTopLevelUpdate;
402
+
403
function getStateFromUpdate(update, instance, prevState, props) {
404
const partialState = update.partialState;
405
if (typeof partialState === 'function') {
@@ -412,12 +459,14 @@ function beginUpdateQueue(
459
if (update.isForced) {
460
queue.hasForceUpdate = true;
461
}
415
- if (update.callback) {
462
+ // Second condition ignores top-level unmount callbacks if they are not the
463
+ // last update in the queue, since a subsequent update will cause a remount.
464
+ if (update.callback && !(update.isTopLevelUnmount && update.next)) {
465
+ const callbackUpdate = cloneUpdate(update);
466
if (callbackList && callbackList.last) {
417
- callbackList.last.next = update;
418
- callbackList.last = update;
467
+ callbackList.last.next = callbackUpdate;
468
+ callbackList.last = callbackUpdate;
469
} else {
420
- const callbackUpdate = cloneUpdate(update);
470
callbackList = {
471
first: callbackUpdate,
472
last: callbackUpdate,
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
+1
@@ -654,6 +654,7 @@ describe('ReactIncrementalErrorHandling', () => {
654
ReactNoop.unmountRootWithID('d');
655
ReactNoop.unmountRootWithID('e');
656
ReactNoop.unmountRootWithID('f');
657
+ ReactNoop.flush();
658
expect(ReactNoop.getChildren('a')).toEqual(null);
659
expect(ReactNoop.getChildren('b')).toEqual(null);
660
expect(ReactNoop.getChildren('c')).toEqual(null);