Skip to content

Commit 0d26630

Browse files
sebmarkbageacusti
authored andcommitted
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 9b98b84 commit 0d26630

File tree

8 files changed

+76
-60
lines changed

8 files changed

+76
-60
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,3 @@ src/renderers/shared/stack/reconciler/__tests__/refs-test.js
120120

121121
src/test/__tests__/ReactTestUtils-test.js
122122
* traverses children in the correct order
123-
* should support injected wrapper components as DOM components

scripts/fiber/tests-passing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,7 @@ src/test/__tests__/ReactTestUtils-test.js
15601560
* can scryRenderedDOMComponentsWithClass with TextComponent
15611561
* can scryRenderedDOMComponentsWithClass with className contains \n
15621562
* can scryRenderedDOMComponentsWithClass with multiple classes
1563+
* should support injected wrapper components as DOM components
15631564
* should change the value of an input field
15641565
* should change the value of an input field in a component
15651566
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ var {
3838
} = ReactDOMFiberComponent;
3939
var { precacheFiberNode } = ReactDOMComponentTree;
4040

41+
const DOCUMENT_NODE = 9;
42+
4143
ReactDOMInjection.inject();
4244
ReactControlledComponent.injection.injectFiberControlledHostComponent(
4345
ReactDOMFiberComponent
@@ -88,19 +90,13 @@ var DOMRenderer = ReactFiberReconciler({
8890
eventsEnabled = null;
8991
},
9092

91-
updateContainer(container : Container, children : HostChildren<Instance | TextInstance>) : void {
92-
// TODO: Containers should update similarly to other parents.
93-
container.innerHTML = '';
94-
recursivelyAppendChildren(container, children);
95-
},
96-
9793
createInstance(
9894
type : string,
9995
props : Props,
10096
children : HostChildren<Instance | TextInstance>,
10197
internalInstanceHandle : Object
10298
) : Instance {
103-
const root = document.body; // HACK
99+
const root = document.documentElement; // HACK
104100

105101
const domElement : Instance = createElement(type, props, root);
106102
precacheFiberNode(internalInstanceHandle, domElement);
@@ -124,7 +120,7 @@ var DOMRenderer = ReactFiberReconciler({
124120
internalInstanceHandle : Object
125121
) : void {
126122
var type = domElement.tagName.toLowerCase(); // HACK
127-
var root = document.body; // HACK
123+
var root = document.documentElement; // HACK
128124
// Update the internal instance handle so that we know which props are
129125
// the current ones.
130126
precacheFiberNode(internalInstanceHandle, domElement);
@@ -141,19 +137,19 @@ var DOMRenderer = ReactFiberReconciler({
141137
textInstance.nodeValue = newText;
142138
},
143139

144-
appendChild(parentInstance : Instance, child : Instance | TextInstance) : void {
140+
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
145141
parentInstance.appendChild(child);
146142
},
147143

148144
insertBefore(
149-
parentInstance : Instance,
145+
parentInstance : Instance | Container,
150146
child : Instance | TextInstance,
151147
beforeChild : Instance | TextInstance
152148
) : void {
153149
parentInstance.insertBefore(child, beforeChild);
154150
},
155151

156-
removeChild(parentInstance : Instance, child : Instance | TextInstance) : void {
152+
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
157153
parentInstance.removeChild(child);
158154
},
159155

@@ -177,9 +173,15 @@ function warnAboutUnstableUse() {
177173
warned = true;
178174
}
179175

180-
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
176+
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
177+
let container : DOMContainerElement =
178+
containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
181179
let root;
182180
if (!container._reactRootContainer) {
181+
// First clear any existing content.
182+
while (container.lastChild) {
183+
container.removeChild(container.lastChild);
184+
}
183185
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
184186
} else {
185187
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
@@ -194,12 +196,12 @@ var ReactDOM = {
194196
return renderSubtreeIntoContainer(null, element, container, callback);
195197
},
196198

197-
unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
199+
unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
198200
invariant(
199201
parentComponent != null && ReactInstanceMap.has(parentComponent),
200202
'parentComponent must be a valid React Component'
201203
);
202-
return renderSubtreeIntoContainer(parentComponent, element, container, callback);
204+
return renderSubtreeIntoContainer(parentComponent, element, containerNode, callback);
203205
},
204206

205207
unmountComponentAtNode(container : DOMContainerElement) {

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;
@@ -142,6 +148,10 @@ module.exports = function<T, P, I, TI, C>(
142148
} else {
143149
appendChild(parent, node.stateNode);
144150
}
151+
} else if (node.tag === Portal) {
152+
// If the insertion itself is a portal, then we don't want to traverse
153+
// down its children. Instead, we'll get insertions from each child in
154+
// the portal directly.
145155
} else if (node.child) {
146156
// TODO: Coroutines need to visit the stateNode.
147157
node = node.child;
@@ -201,6 +211,18 @@ module.exports = function<T, P, I, TI, C>(
201211
if (parent) {
202212
removeChild(parent, node.stateNode);
203213
}
214+
} else if (node.tag === Portal) {
215+
// If this is a portal, then the parent is actually the portal itself.
216+
// We need to keep track of which parent we're removing from.
217+
// TODO: This uses a recursive call. We can get rid of that by mutating
218+
// the parent binding and restoring it by searching for the host parent
219+
// again when we pop past a portal.
220+
const portalParent = node.stateNode.containerInfo;
221+
let child = node.child;
222+
while (child) {
223+
unmountHostComponents(portalParent, child);
224+
child = child.sibling;
225+
}
204226
} else {
205227
commitUnmount(node);
206228
if (node.child) {
@@ -260,11 +282,6 @@ module.exports = function<T, P, I, TI, C>(
260282
detachRef(current);
261283
return;
262284
}
263-
case Portal: {
264-
const containerInfo : C = current.stateNode.containerInfo;
265-
updateContainer(containerInfo, null);
266-
return;
267-
}
268285
}
269286
}
270287

@@ -274,14 +291,6 @@ module.exports = function<T, P, I, TI, C>(
274291
detachRefIfNeeded(current, finishedWork);
275292
return;
276293
}
277-
case HostContainer: {
278-
// TODO: Attach children to root container.
279-
const children = finishedWork.output;
280-
const root : FiberRoot = finishedWork.stateNode;
281-
const containerInfo : C = root.containerInfo;
282-
updateContainer(containerInfo, children);
283-
return;
284-
}
285294
case HostComponent: {
286295
const instance : I = finishedWork.stateNode;
287296
if (instance != null && current) {
@@ -303,10 +312,10 @@ module.exports = function<T, P, I, TI, C>(
303312
commitTextUpdate(textInstance, oldText, newText);
304313
return;
305314
}
315+
case HostContainer: {
316+
return;
317+
}
306318
case Portal: {
307-
const children = finishedWork.child;
308-
const containerInfo : C = finishedWork.stateNode.containerInfo;
309-
updateContainer(containerInfo, children);
310319
return;
311320
}
312321
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
@@ -47,22 +47,16 @@ type OpaqueNode = Fiber;
4747

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

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

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

63-
appendChild(parentInstance : I, child : I | TI) : void,
64-
insertBefore(parentInstance : I, child : I | TI, beforeChild : I | TI) : void,
65-
removeChild(parentInstance : I, child : I | TI) : void,
57+
appendChild(parentInstance : I | C, child : I | TI) : void,
58+
insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void,
59+
removeChild(parentInstance : I | C, child : I | TI) : void,
6660

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

0 commit comments

Comments
 (0)