Skip to content

Commit 8dab603

Browse files
committed
Update root children using the appendChild/insertBefore/removeChild methods
This removes updateContainer and instead uses the regular child mutation methods to insert into the root container and portals. Since we're no longer clearing out the container DOM in updateContainer we have to do that manually during initial mount. This now works on a document and one of the tests end up unmounting the body when you render into the document so I had to work around that bit since we don't yet properly support rendering into the document root.
1 parent 7ef856a commit 8dab603

File tree

8 files changed

+75
-60
lines changed

8 files changed

+75
-60
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
4545
src/renderers/dom/shared/__tests__/ReactEventListener-test.js
4646
* should batch between handlers from different roots
4747

48-
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
49-
* should control a value in reentrant events
50-
5148
src/renderers/dom/stack/client/__tests__/ReactDOM-test.js
5249
* throws in render() if the mount callback is not a function
5350
* throws in render() if the update callback is not a function
@@ -124,4 +121,3 @@ src/renderers/shared/stack/reconciler/__tests__/refs-test.js
124121

125122
src/test/__tests__/ReactTestUtils-test.js
126123
* traverses children in the correct order
127-
* should support injected wrapper components as DOM components

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMIframe-test.js
811811

812812
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
813813
* should properly control a value even if no event listener exists
814+
* should control a value in reentrant events
814815
* should control values in reentrant events with different targets
815816
* should display `defaultValue` of number 0
816817
* only assigns defaultValue if it changes
@@ -1555,6 +1556,7 @@ src/test/__tests__/ReactTestUtils-test.js
15551556
* can scryRenderedDOMComponentsWithClass with TextComponent
15561557
* can scryRenderedDOMComponentsWithClass with className contains \n
15571558
* can scryRenderedDOMComponentsWithClass with multiple classes
1559+
* should support injected wrapper components as DOM components
15581560
* should change the value of an input field
15591561
* should change the value of an input field in a component
15601562
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ var {
4141
} = ReactDOMFiberComponent;
4242
var { precacheFiberNode } = ReactDOMComponentTree;
4343

44+
const DOCUMENT_NODE = 9;
45+
4446
ReactDOMInjection.inject();
4547
ReactControlledComponent.injection.injectFiberControlledHostComponent(
4648
ReactDOMFiberComponent
@@ -75,19 +77,13 @@ function recursivelyAppendChildren(parent : Element, child : HostChildren<Instan
7577

7678
var DOMRenderer = ReactFiberReconciler({
7779

78-
updateContainer(container : Container, children : HostChildren<Instance | TextInstance>) : void {
79-
// TODO: Containers should update similarly to other parents.
80-
container.innerHTML = '';
81-
recursivelyAppendChildren(container, children);
82-
},
83-
8480
createInstance(
8581
type : string,
8682
props : Props,
8783
children : HostChildren<Instance | TextInstance>,
8884
internalInstanceHandle : Object
8985
) : Instance {
90-
const root = document.body; // HACK
86+
const root = document.documentElement; // HACK
9187

9288
const domElement : Instance = createElement(type, props, root);
9389
precacheFiberNode(internalInstanceHandle, domElement);
@@ -111,7 +107,7 @@ var DOMRenderer = ReactFiberReconciler({
111107
internalInstanceHandle : Object
112108
) : void {
113109
var type = domElement.tagName.toLowerCase(); // HACK
114-
var root = document.body; // HACK
110+
var root = document.documentElement; // HACK
115111
// Update the internal instance handle so that we know which props are
116112
// the current ones.
117113
precacheFiberNode(internalInstanceHandle, domElement);
@@ -128,19 +124,19 @@ var DOMRenderer = ReactFiberReconciler({
128124
textInstance.nodeValue = newText;
129125
},
130126

131-
appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
127+
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
132128
parentInstance.appendChild(child);
133129
},
134130

135131
insertBefore(
136-
parentInstance : Instance,
132+
parentInstance : Instance | Container,
137133
child : Instance | TextInstance,
138134
beforeChild : Instance | TextInstance
139135
) : void {
140136
parentInstance.insertBefore(child, beforeChild);
141137
},
142138

143-
removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
139+
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
144140
parentInstance.removeChild(child);
145141
},
146142

@@ -165,8 +161,15 @@ function warnAboutUnstableUse() {
165161
}
166162

167163
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
164+
if (container.nodeType === DOCUMENT_NODE) {
165+
container = container.documentElement;
166+
}
168167
let root;
169168
if (!container._reactRootContainer) {
169+
// First clear any existing content.
170+
while (container.lastChild) {
171+
container.removeChild(container.lastChild);
172+
}
170173
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
171174
} else {
172175
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);

src/renderers/noop/ReactNoop.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ function flattenChildren(children : HostChildren<Instance | TextInstance>) {
6767

6868
var NoopRenderer = ReactFiberReconciler({
6969

70-
updateContainer(containerInfo : Container, children : HostChildren<Instance | TextInstance>) : void {
71-
containerInfo.children = flattenChildren(children);
72-
},
73-
7470
createInstance(type : string, props : Props, children : HostChildren<Instance | TextInstance>) : Instance {
7571
const inst = {
7672
tag: TERMINAL_TAG,
@@ -104,7 +100,7 @@ var NoopRenderer = ReactFiberReconciler({
104100
textInstance.text = newText;
105101
},
106102

107-
appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
103+
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
108104
const index = parentInstance.children.indexOf(child);
109105
if (index !== -1) {
110106
parentInstance.children.splice(index, 1);
@@ -113,7 +109,7 @@ var NoopRenderer = ReactFiberReconciler({
113109
},
114110

115111
insertBefore(
116-
parentInstance : Instance,
112+
parentInstance : Instance | Container,
117113
child : Instance | TextInstance,
118114
beforeChild : Instance | TextInstance
119115
) : void {
@@ -128,7 +124,7 @@ var NoopRenderer = ReactFiberReconciler({
128124
parentInstance.children.splice(beforeIndex, 0, child);
129125
},
130126

131-
removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
127+
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
132128
const index = parentInstance.children.indexOf(child);
133129
if (index === -1) {
134130
throw new Error('This child does not exist.');

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,24 @@ module.exports = function<T, P, I, TI, C>(
300300
}
301301

302302
function updatePortalComponent(current, workInProgress) {
303-
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
303+
const priorityLevel = workInProgress.pendingWorkPriority;
304+
const nextChildren = workInProgress.pendingProps;
305+
if (!current) {
306+
// Portals are special because we don't append the children during mount
307+
// but at commit. Therefore we need to track insertions which the normal
308+
// flow doesn't do during mount. This doesn't happen at the root because
309+
// the root always starts with a "current" with a null child.
310+
// TODO: Consider unifying this with how the root works.
311+
workInProgress.child = reconcileChildFibersInPlace(
312+
workInProgress,
313+
workInProgress.child,
314+
nextChildren,
315+
priorityLevel
316+
);
317+
markChildAsProgressed(current, workInProgress, priorityLevel);
318+
} else {
319+
reconcileChildren(current, workInProgress, nextChildren);
320+
}
304321
}
305322

306323
/*

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
'use strict';
1414

1515
import type { Fiber } from 'ReactFiber';
16-
import type { FiberRoot } from 'ReactFiberRoot';
1716
import type { HostConfig } from 'ReactFiberReconciler';
1817

1918
var ReactTypeOfWork = require('ReactTypeOfWork');
@@ -37,7 +36,6 @@ module.exports = function<T, P, I, TI, C>(
3736
trapError : (failedFiber : Fiber, error: Error, isUnmounting : boolean) => void
3837
) {
3938

40-
const updateContainer = config.updateContainer;
4139
const commitUpdate = config.commitUpdate;
4240
const commitTextUpdate = config.commitTextUpdate;
4341

@@ -68,22 +66,30 @@ module.exports = function<T, P, I, TI, C>(
6866
}
6967
}
7068

71-
function getHostParent(fiber : Fiber) : ?I {
69+
function getHostParent(fiber : Fiber) : null | I | C {
7270
let parent = fiber.return;
7371
while (parent) {
7472
switch (parent.tag) {
7573
case HostComponent:
7674
return parent.stateNode;
7775
case HostContainer:
78-
// TODO: Currently we use the updateContainer feature to update these,
79-
// but we should be able to handle this case too.
80-
return null;
76+
return parent.stateNode.containerInfo;
77+
case Portal:
78+
return parent.stateNode.containerInfo;
8179
}
8280
parent = parent.return;
8381
}
8482
return null;
8583
}
8684

85+
function isHostParent(fiber : Fiber) : boolean {
86+
return (
87+
fiber.tag === HostComponent ||
88+
fiber.tag === HostContainer ||
89+
fiber.tag === Portal
90+
);
91+
}
92+
8793
function getHostSibling(fiber : Fiber) : ?I {
8894
// We're going to search forward into the tree until we find a sibling host
8995
// node. Unfortunately, if multiple insertions are done in a row we have to
@@ -93,7 +99,7 @@ module.exports = function<T, P, I, TI, C>(
9399
siblings: while (true) {
94100
// If we didn't find anything, let's try the next sibling.
95101
while (!node.sibling) {
96-
if (!node.return || node.return.tag === HostComponent) {
102+
if (!node.return || isHostParent(node.return)) {
97103
// If we pop out of the root or hit the parent the fiber we are the
98104
// last sibling.
99105
return null;
@@ -140,6 +146,10 @@ module.exports = function<T, P, I, TI, C>(
140146
} else {
141147
appendChild(parent, node.stateNode);
142148
}
149+
} else if (node.tag === Portal) {
150+
// If the insertion itself is a portal, then we don't want to traverse
151+
// down its children. Instead, we'll get insertions from each child in
152+
// the portal directly.
143153
} else if (node.child) {
144154
// TODO: Coroutines need to visit the stateNode.
145155
node = node.child;
@@ -197,6 +207,18 @@ module.exports = function<T, P, I, TI, C>(
197207
if (parent) {
198208
removeChild(parent, node.stateNode);
199209
}
210+
} else if (node.tag === Portal) {
211+
// If this is a portal, then the parent is actually the portal itself.
212+
// We need to keep track of which parent we're removing from.
213+
// TODO: This uses a recursive call. We can get rid of that by mutating
214+
// the parent binding and restoring it by searching for the host parent
215+
// again when we pop past a portal.
216+
const portalParent = node.stateNode.containerInfo;
217+
let child = node.child;
218+
while (child) {
219+
unmountHostComponents(portalParent, child);
220+
child = child.sibling;
221+
}
200222
} else {
201223
commitUnmount(node);
202224
if (node.child) {
@@ -254,11 +276,6 @@ module.exports = function<T, P, I, TI, C>(
254276
detachRef(current);
255277
return;
256278
}
257-
case Portal: {
258-
const containerInfo : C = current.stateNode.containerInfo;
259-
updateContainer(containerInfo, null);
260-
return;
261-
}
262279
}
263280
}
264281

@@ -268,14 +285,6 @@ module.exports = function<T, P, I, TI, C>(
268285
detachRefIfNeeded(current, finishedWork);
269286
return;
270287
}
271-
case HostContainer: {
272-
// TODO: Attach children to root container.
273-
const children = finishedWork.output;
274-
const root : FiberRoot = finishedWork.stateNode;
275-
const containerInfo : C = root.containerInfo;
276-
updateContainer(containerInfo, children);
277-
return;
278-
}
279288
case HostComponent: {
280289
const instance : I = finishedWork.stateNode;
281290
if (instance != null && current) {
@@ -297,10 +306,10 @@ module.exports = function<T, P, I, TI, C>(
297306
commitTextUpdate(textInstance, oldText, newText);
298307
return;
299308
}
309+
case HostContainer: {
310+
return;
311+
}
300312
case Portal: {
301-
const children = finishedWork.child;
302-
const containerInfo : C = finishedWork.stateNode.containerInfo;
303-
updateContainer(containerInfo, children);
304313
return;
305314
}
306315
default:

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
167167
fiberRoot.context = fiberRoot.pendingContext;
168168
fiberRoot.pendingContext = null;
169169
}
170-
// We don't know if a container has updated any children so we always
171-
// need to update it right now. We schedule this side-effect before
172-
// all the other side-effects in the subtree. We need to schedule it
173-
// before so that the entire tree is up-to-date before the life-cycles
174-
// are invoked.
170+
// TODO: Only mark this as an update if we have any pending callbacks
171+
// on it.
175172
markUpdate(workInProgress);
176173
return null;
177174
}
@@ -253,6 +250,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
253250
transferOutput(workInProgress.child, workInProgress);
254251
return null;
255252
case Portal:
253+
// TODO: Only mark this as an update if we have any pending callbacks.
256254
markUpdate(workInProgress);
257255
workInProgress.output = null;
258256
workInProgress.memoizedProps = workInProgress.pendingProps;

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,16 @@ type OpaqueNode = Fiber;
4646

4747
export type HostConfig<T, P, I, TI, C> = {
4848

49-
// TODO: We don't currently have a quick way to detect that children didn't
50-
// reorder so we host will always need to check the set. We should make a flag
51-
// or something so that it can bailout easily.
52-
53-
updateContainer(containerInfo : C, children : HostChildren<I | TI>) : void,
54-
5549
createInstance(type : T, props : P, children : HostChildren<I | TI>, internalInstanceHandle : OpaqueNode) : I,
5650
prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
5751
commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
5852

5953
createTextInstance(text : string, internalInstanceHandle : OpaqueNode) : TI,
6054
commitTextUpdate(textInstance : TI, oldText : string, newText : string) : void,
6155

62-
appendChild(parentInstance : I, child : I | TI) : void,
63-
insertBefore(parentInstance : I, child : I | TI, beforeChild : I | TI) : void,
64-
removeChild(parentInstance : I, child : I | TI) : void,
56+
appendChild(parentInstance : I | C, child : I | TI) : void,
57+
insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void,
58+
removeChild(parentInstance : I | C, child : I | TI) : void,
6559

6660
scheduleAnimationCallback(callback : () => void) : void,
6761
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void,

0 commit comments

Comments
 (0)