Skip to content

Commit 1d23b4e

Browse files
author
Brian Vaughn
committed
Renderers can schedule commit-time effects for initial mount
The finalizeInitialChildren HostConfig method now utilizes a boolean return type. Renderers can return true to indicate that custom effects should be processed at commit-time after host components have been mounted. This type of work is marked using the existing Update flag. A new HostConfig method, commitInitialEffects, has been added as well for performing this work.
1 parent b5ed656 commit 1d23b4e

File tree

8 files changed

+93
-76
lines changed

8 files changed

+93
-76
lines changed

src/renderers/art/ReactARTFiber.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,10 @@ const ARTRenderer = ReactFiberReconciler({
414414
// Noop
415415
},
416416

417+
commitInitialEffects(instance, type, newProps) {
418+
// Noop
419+
},
420+
417421
commitUpdate(instance, type, oldProps, newProps) {
418422
instance._applyProps(instance, newProps, oldProps);
419423
},
@@ -457,11 +461,7 @@ const ARTRenderer = ReactFiberReconciler({
457461
},
458462

459463
finalizeInitialChildren(domElement, type, props) {
460-
// Noop
461-
},
462-
463-
focusHostComponent(instance) {
464-
// Noop
464+
return false;
465465
},
466466

467467
insertBefore(parentInstance, child, beforeChild) {
@@ -507,10 +507,6 @@ const ARTRenderer = ReactFiberReconciler({
507507

508508
scheduleDeferredCallback: window.requestIdleCallback,
509509

510-
shouldFocusHostComponent(type, props) {
511-
return false;
512-
},
513-
514510
shouldSetTextContent(props) {
515511
return (
516512
typeof props.children === 'string' ||

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ function validateContainer(container) {
9696
}
9797
}
9898

99+
function shouldAutoFocusHostComponent(
100+
type : string,
101+
props : Props,
102+
) : boolean {
103+
switch (type) {
104+
case 'button':
105+
case 'input':
106+
case 'select':
107+
case 'textarea':
108+
return !!(props : any).autoFocus;
109+
}
110+
return false;
111+
}
112+
99113
var DOMRenderer = ReactFiberReconciler({
100114

101115
getRootHostContext(rootContainerInstance : Container) : HostContext {
@@ -173,8 +187,9 @@ var DOMRenderer = ReactFiberReconciler({
173187
type : string,
174188
props : Props,
175189
rootContainerInstance : Container,
176-
) : void {
190+
) : boolean {
177191
setInitialProperties(domElement, type, props, rootContainerInstance);
192+
return shouldAutoFocusHostComponent(type, props);
178193
},
179194

180195
prepareUpdate(
@@ -197,6 +212,18 @@ var DOMRenderer = ReactFiberReconciler({
197212
return true;
198213
},
199214

215+
commitInitialEffects(
216+
domElement : Instance,
217+
type : string,
218+
newProps : Props,
219+
rootContainerInstance : Container,
220+
internalInstanceHandle : Object,
221+
) : void {
222+
if (shouldAutoFocusHostComponent(type, newProps)) {
223+
(domElement : any).focus();
224+
}
225+
},
226+
200227
commitUpdate(
201228
domElement : Instance,
202229
type : string,
@@ -262,26 +289,6 @@ var DOMRenderer = ReactFiberReconciler({
262289
parentInstance.removeChild(child);
263290
},
264291

265-
shouldFocusHostComponent(
266-
type : string,
267-
props : Props,
268-
) : boolean {
269-
switch (type) {
270-
case 'button':
271-
case 'input':
272-
case 'select':
273-
case 'textarea':
274-
return !!(props : any).autoFocus;
275-
}
276-
return false;
277-
},
278-
279-
focusHostComponent(
280-
domElement : Instance,
281-
) : void {
282-
(domElement : any).focus();
283-
},
284-
285292
scheduleAnimationCallback: window.requestAnimationFrame,
286293

287294
scheduleDeferredCallback: window.requestIdleCallback,

src/renderers/native/ReactNativeFiber.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ const NativeRenderer = ReactFiberReconciler({
110110
);
111111
},
112112

113+
commitInitialEffects(
114+
instance : Instance,
115+
type : string,
116+
newProps : Props,
117+
rootContainerInstance : Object,
118+
internalInstanceHandle : Object
119+
) : void {
120+
// Noop
121+
},
122+
113123
commitUpdate(
114124
instance : Instance,
115125
type : string,
@@ -197,7 +207,7 @@ const NativeRenderer = ReactFiberReconciler({
197207
type : string,
198208
props : Props,
199209
rootContainerInstance : Container,
200-
) : void {
210+
) : boolean {
201211
// Map from child objects to native tags.
202212
// Either way we need to pass a copy of the Array to prevent it from being frozen.
203213
const nativeTags = parentInstance._children.map(
@@ -210,6 +220,8 @@ const NativeRenderer = ReactFiberReconciler({
210220
parentInstance._nativeTag, // containerTag
211221
nativeTags // reactTags
212222
);
223+
224+
return false;
213225
},
214226

215227
focusHostComponent(instance : Instance) : void {
@@ -326,10 +338,6 @@ const NativeRenderer = ReactFiberReconciler({
326338

327339
scheduleDeferredCallback: global.requestIdleCallback,
328340

329-
shouldFocusHostComponent(type : string, props : Props) : boolean {
330-
return false;
331-
},
332-
333341
shouldSetTextContent(props : Props) : boolean {
334342
// TODO (bvaughn) Revisit this decision.
335343
// Always returning false simplifies the createInstance() implementation,

src/renderers/noop/ReactNoop.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,18 @@ var NoopRenderer = ReactFiberReconciler({
7070
parentInstance.children.push(child);
7171
},
7272

73-
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
74-
// Noop
73+
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : boolean {
74+
return false;
7575
},
7676

7777
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : boolean {
7878
return true;
7979
},
8080

81+
commitInitialEffects(instance : Instance, type : string, newProps : Props) : void {
82+
// Noop
83+
},
84+
8185
commitUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : void {
8286
instance.prop = newProps.prop;
8387
},
@@ -165,13 +169,6 @@ var NoopRenderer = ReactFiberReconciler({
165169
resetAfterCommit() : void {
166170
},
167171

168-
shouldFocusHostComponent(type : string, props : Props) : boolean {
169-
return false;
170-
},
171-
172-
focusHostComponent(instance : Instance) : void {
173-
},
174-
175172
});
176173

177174
var rootContainers = new Map();

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ var {
3939
Fragment,
4040
} = ReactTypeOfWork;
4141
var {
42-
Focus,
4342
Update,
4443
} = ReactTypeOfSideEffect;
4544

@@ -57,7 +56,6 @@ module.exports = function<T, P, I, TI, C, CX>(
5756
appendInitialChild,
5857
finalizeInitialChildren,
5958
prepareUpdate,
60-
shouldFocusHostComponent,
6159
} = config;
6260

6361
const {
@@ -242,15 +240,14 @@ module.exports = function<T, P, I, TI, C, CX>(
242240
workInProgress
243241
);
244242

245-
// Certain renderers (eg DOM) support focusable components.
246-
// It is not safe to focus components until they have been mounted though.
247-
// For now we should just mark them for later work.
248-
if (shouldFocusHostComponent(type, newProps)) {
249-
workInProgress.effectTag |= Focus;
250-
}
251-
252243
appendAllChildren(instance, workInProgress);
253-
finalizeInitialChildren(instance, type, newProps, rootContainerInstance);
244+
245+
// Certain renderers require commit-time effects for initial mount.
246+
// (eg DOM renderer supports auto-focus for certain elements).
247+
// Make sure such renderers get scheduled for later work.
248+
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
249+
workInProgress.effectTag |= Update;
250+
}
254251

255252
workInProgress.stateNode = instance;
256253
if (workInProgress.ref) {

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ export type HostConfig<T, P, I, TI, C, CX> = {
5050

5151
createInstance(type : T, props : P, rootContainerInstance : C, hostContext : CX, internalInstanceHandle : OpaqueNode) : I,
5252
appendInitialChild(parentInstance : I, child : I | TI) : void,
53-
finalizeInitialChildren(parentInstance : I, type : T, props : P, rootContainerInstance : C) : void,
53+
finalizeInitialChildren(parentInstance : I, type : T, props : P, rootContainerInstance : C) : boolean,
5454

5555
prepareUpdate(instance : I, type : T, oldProps : P, newProps : P, hostContext : CX) : boolean,
5656
commitUpdate(instance : I, type : T, oldProps : P, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
57+
commitInitialEffects(instance : I, type : T, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
5758

5859
shouldSetTextContent(props : P) : boolean,
5960
resetTextContent(instance : I) : void,
@@ -72,9 +73,6 @@ export type HostConfig<T, P, I, TI, C, CX> = {
7273
resetAfterCommit() : void,
7374

7475
useSyncScheduling ?: boolean,
75-
76-
shouldFocusHostComponent(type : T, props : P) : boolean,
77-
focusHostComponent(instance : I) : void,
7876
};
7977

8078
export type Reconciler<C, I, TI> = {

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ var {
4141
} = require('ReactPriorityLevel');
4242

4343
var {
44-
Focus,
4544
NoEffect,
4645
Placement,
4746
Update,
@@ -76,7 +75,12 @@ var timeHeuristicForUnitOfWork = 1;
7675

7776
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
7877
const hostContext = ReactFiberHostContext(config);
79-
const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
78+
const {
79+
getRootHostContainer,
80+
popHostContainer,
81+
popHostContext,
82+
resetHostContainer,
83+
} = hostContext;
8084
const { beginWork, beginFailedWork } = ReactFiberBeginWork(
8185
config,
8286
hostContext,
@@ -96,7 +100,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
96100
useSyncScheduling,
97101
prepareForCommit,
98102
resetAfterCommit,
99-
focusHostComponent,
103+
commitInitialEffects,
100104
} = config;
101105

102106
// The priority level to use when scheduling an update.
@@ -285,12 +289,23 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
285289
}
286290
}
287291

288-
function commitAllLifeCycles() {
292+
function commitAllLifeCyclesAndApplyInitialEffects() {
289293
while (nextEffect) {
290294
const current = nextEffect.alternate;
291295

292-
if (nextEffect.effectTag & Focus) {
293-
focusHostComponent(nextEffect.stateNode);
296+
// Renderers may schedule work to be done after host components are mounted
297+
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
298+
// These effects should only be committed when components are first mounted,
299+
// aka when there is no current/alternate.
300+
if (
301+
!current &&
302+
nextEffect.effectTag & Update
303+
) {
304+
const type = nextEffect.type;
305+
const props = nextEffect.memoizedProps;
306+
const instance : I = nextEffect.stateNode;
307+
const rootContainerInstance = getRootHostContainer();
308+
commitInitialEffects(instance, type, props, rootContainerInstance, nextEffect);
294309
}
295310

296311
// Use Task priority for lifecycle updates
@@ -378,11 +393,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
378393
// In the second pass we'll perform all life-cycles and ref callbacks.
379394
// Life-cycles happen as a separate pass so that all placements, updates,
380395
// and deletions in the entire tree have already been invoked.
381-
// This pass also triggers focus since components must be mounted first.
396+
// This pass also triggers any renderer-specific initial effects.
382397
nextEffect = firstEffect;
383398
while (nextEffect) {
384399
try {
385-
commitAllLifeCycles(finishedWork, nextEffect);
400+
commitAllLifeCyclesAndApplyInitialEffects(finishedWork, nextEffect);
386401
} catch (error) {
387402
if (!nextEffect) {
388403
throw new Error('Should have nextEffect.');

src/renderers/shared/fiber/ReactTypeOfSideEffect.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@
1212

1313
'use strict';
1414

15-
export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 16 | 32 | 64;
15+
export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 16 | 32;
1616

1717
module.exports = {
18-
NoEffect: 0, // 0b0000000
19-
Placement: 1, // 0b0000001
20-
Update: 2, // 0b0000010
21-
PlacementAndUpdate: 3, // 0b0000011
22-
Deletion: 4, // 0b0000100
23-
ContentReset: 8, // 0b0001000
24-
Callback: 16, // 0b0010000
25-
Focus: 32, // 0b0100000
26-
Err: 64, // 0b1000000
18+
NoEffect: 0, // 0b000000
19+
Placement: 1, // 0b000001
20+
Update: 2, // 0b000010
21+
PlacementAndUpdate: 3, // 0b000011
22+
Deletion: 4, // 0b000100
23+
ContentReset: 8, // 0b001000
24+
Callback: 16, // 0b010000
25+
Err: 32, // 0b100000
2726
};

0 commit comments

Comments
 (0)