Skip to content

Commit 13980e6

Browse files
authored
Fiber: Fix reentrant mounting in synchronous mode (#8623)
1 parent c978f78 commit 13980e6

File tree

9 files changed

+71
-37
lines changed

9 files changed

+71
-37
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,7 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
15681568
* does not update one component twice in a batch (#6371)
15691569
* unstable_batchedUpdates should return value from a callback
15701570
* unmounts and remounts a root in the same batch
1571+
* handles reentrant mounting in synchronous mode
15711572

15721573
src/renderers/shared/shared/__tests__/refs-destruction-test.js
15731574
* should remove refs when destroying the parent

src/renderers/art/ReactARTFiber.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,10 @@ class Surface extends Component {
328328

329329
this._surface = Mode.Surface(+width, +height, this._tagRef);
330330

331-
this._mountNode = ARTRenderer.mountContainer(
331+
this._mountNode = ARTRenderer.createContainer(this._surface);
332+
ARTRenderer.updateContainer(
332333
this.props.children,
333-
this._surface,
334+
this._mountNode,
334335
this,
335336
);
336337
}

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,15 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
289289

290290
let container : DOMContainerElement =
291291
containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
292-
let root;
293-
if (!container._reactRootContainer) {
292+
let root = container._reactRootContainer;
293+
if (!root) {
294294
// First clear any existing content.
295295
while (container.lastChild) {
296296
container.removeChild(container.lastChild);
297297
}
298-
root = container._reactRootContainer = DOMRenderer.mountContainer(children, container, parentComponent, callback);
299-
} else {
300-
DOMRenderer.updateContainer(children, root = container._reactRootContainer, parentComponent, callback);
298+
root = container._reactRootContainer = DOMRenderer.createContainer(container);
301299
}
300+
DOMRenderer.updateContainer(children, root, parentComponent, callback);
302301
return DOMRenderer.getPublicRootInstance(root);
303302
}
304303

src/renderers/native/ReactNativeFiber.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,10 @@ const ReactNative = {
361361
if (!root) {
362362
// TODO (bvaughn): If we decide to keep the wrapper component,
363363
// We could create a wrapper for containerTag as well to reduce special casing.
364-
root = NativeRenderer.mountContainer(element, containerTag, null, callback);
365-
364+
root = NativeRenderer.createContainer(containerTag);
366365
roots.set(containerTag, root);
367-
} else {
368-
NativeRenderer.updateContainer(element, root, null, callback);
369366
}
367+
NativeRenderer.updateContainer(element, root, null, callback);
370368

371369
return NativeRenderer.getPublicRootInstance(root);
372370
},

src/renderers/noop/ReactNoop.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,14 @@ var ReactNoop = {
188188
},
189189

190190
renderToRootWithID(element : ReactElement<any>, rootID : string, callback : ?Function) {
191-
if (!roots.has(rootID)) {
191+
let root = roots.get(rootID);
192+
if (!root) {
192193
const container = { rootID: rootID, children: [] };
193194
rootContainers.set(rootID, container);
194-
const root = NoopRenderer.mountContainer(element, container, null, callback);
195+
root = NoopRenderer.createContainer(container);
195196
roots.set(rootID, root);
196-
} else {
197-
const root = roots.get(rootID);
198-
if (root) {
199-
NoopRenderer.updateContainer(element, root, null, callback);
200-
}
201197
}
198+
NoopRenderer.updateContainer(element, root, null, callback);
202199
},
203200

204201
unmountRootWithID(rootID : string) {

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ module.exports = function<T, P, I, TI, C, CX>(
280280
root.pendingContext,
281281
root.pendingContext !== root.context
282282
);
283-
} else {
283+
} else if (root.context) {
284+
// Should always be set
284285
pushTopLevelContextObject(
285286
workInProgress,
286287
root.context,

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export type HostConfig<T, P, I, TI, C, CX> = {
7575
};
7676

7777
export type Reconciler<C, I, TI> = {
78-
mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
78+
createContainer(containerInfo : C) : OpaqueNode,
7979
updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
8080
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
8181
/* eslint-disable no-undef */
@@ -119,17 +119,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
119119

120120
return {
121121

122-
mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
123-
const context = getContextForSubtree(parentComponent);
124-
const root = createFiberRoot(containerInfo, context);
122+
createContainer(containerInfo : C) : OpaqueNode {
123+
const root = createFiberRoot(containerInfo);
125124
const current = root.current;
126125

127-
scheduleTopLevelUpdate(current, element, callback);
128-
129-
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
130-
ReactFiberInstrumentation.debugTool.onMountContainer(root);
131-
}
132-
133126
// It may seem strange that we don't return the root here, but that will
134127
// allow us to have containers that are in the middle of the tree instead
135128
// of being roots.
@@ -141,19 +134,26 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
141134
const root : FiberRoot = (container.stateNode : any);
142135
const current = root.current;
143136

144-
root.pendingContext = getContextForSubtree(parentComponent);
145-
146-
scheduleTopLevelUpdate(current, element, callback);
147-
148137
if (__DEV__) {
149138
if (ReactFiberInstrumentation.debugTool) {
150-
if (element === null) {
139+
if (current.alternate === null) {
140+
ReactFiberInstrumentation.debugTool.onMountContainer(root);
141+
} else if (element === null) {
151142
ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
152143
} else {
153144
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
154145
}
155146
}
156147
}
148+
149+
const context = getContextForSubtree(parentComponent);
150+
if (root.context === null) {
151+
root.context = context;
152+
} else {
153+
root.pendingContext = context;
154+
}
155+
156+
scheduleTopLevelUpdate(current, element, context, callback);
157157
},
158158

159159
performWithPriority,

src/renderers/shared/fiber/ReactFiberRoot.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ export type FiberRoot = {
2626
// The work schedule is a linked list.
2727
nextScheduledRoot: ?FiberRoot,
2828
// Top context object, used by renderSubtreeIntoContainer
29-
context: Object,
29+
context: ?Object,
3030
pendingContext: ?Object,
3131
};
3232

33-
exports.createFiberRoot = function(containerInfo : any, context : Object) : FiberRoot {
33+
exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
3434
// Cyclic construction. This cheats the type system right now because
3535
// stateNode is any.
3636
const uninitializedFiber = createHostRootFiber();
@@ -40,7 +40,7 @@ exports.createFiberRoot = function(containerInfo : any, context : Object) : Fibe
4040
isScheduled: false,
4141
nextScheduledRoot: null,
4242
callbackList: null,
43-
context: context,
43+
context: null,
4444
pendingContext: null,
4545
};
4646
uninitializedFiber.stateNode = root;

src/renderers/shared/shared/__tests__/ReactUpdates-test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,4 +1105,41 @@ describe('ReactUpdates', () => {
11051105
});
11061106
expect(container.textContent).toBe('b');
11071107
});
1108+
1109+
it('handles reentrant mounting in synchronous mode', () => {
1110+
var mounts = 0;
1111+
class Editor extends React.Component {
1112+
render() {
1113+
return <div>{this.props.text}</div>;
1114+
}
1115+
componentDidMount() {
1116+
mounts++;
1117+
// This should be called only once but we guard just in case.
1118+
if (!this.props.rendered) {
1119+
this.props.onChange({rendered: true});
1120+
}
1121+
}
1122+
}
1123+
1124+
var container = document.createElement('div');
1125+
function render() {
1126+
ReactDOM.render(
1127+
<Editor
1128+
onChange={(newProps) => {
1129+
props = {...props, ...newProps};
1130+
render();
1131+
}}
1132+
{...props}
1133+
/>,
1134+
container
1135+
);
1136+
}
1137+
1138+
var props = {text: 'hello', rendered: false};
1139+
render();
1140+
props = {...props, text: 'goodbye'};
1141+
render();
1142+
expect(container.textContent).toBe('goodbye');
1143+
expect(mounts).toBe(1);
1144+
});
11081145
});

0 commit comments

Comments
 (0)