Skip to content

Commit

Permalink
Renderers can schedule commit-time effects for initial mount
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Brian Vaughn committed Dec 28, 2016
1 parent b5ed656 commit d2dd297
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 73 deletions.
14 changes: 5 additions & 9 deletions src/renderers/art/ReactARTFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ const ARTRenderer = ReactFiberReconciler({
// Noop
},

commitInitialEffects(instance, type, newProps) {
// Noop
},

commitUpdate(instance, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
Expand Down Expand Up @@ -457,11 +461,7 @@ const ARTRenderer = ReactFiberReconciler({
},

finalizeInitialChildren(domElement, type, props) {
// Noop
},

focusHostComponent(instance) {
// Noop
return false;
},

insertBefore(parentInstance, child, beforeChild) {
Expand Down Expand Up @@ -507,10 +507,6 @@ const ARTRenderer = ReactFiberReconciler({

scheduleDeferredCallback: window.requestIdleCallback,

shouldFocusHostComponent(type, props) {
return false;
},

shouldSetTextContent(props) {
return (
typeof props.children === 'string' ||
Expand Down
49 changes: 28 additions & 21 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ function validateContainer(container) {
}
}

function shouldAutoFocusHostComponent(
type : string,
props : Props,
) : boolean {
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!(props : any).autoFocus;
}
return false;
}

var DOMRenderer = ReactFiberReconciler({

getRootHostContext(rootContainerInstance : Container) : HostContext {
Expand Down Expand Up @@ -173,8 +187,9 @@ var DOMRenderer = ReactFiberReconciler({
type : string,
props : Props,
rootContainerInstance : Container,
) : void {
) : boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
},

prepareUpdate(
Expand All @@ -197,6 +212,18 @@ var DOMRenderer = ReactFiberReconciler({
return true;
},

commitInitialEffects(
domElement : Instance,
type : string,
newProps : Props,
rootContainerInstance : Container,
internalInstanceHandle : Object,
) : void {
if (shouldAutoFocusHostComponent(type, newProps)) {
(domElement : any).focus();
}
},

commitUpdate(
domElement : Instance,
type : string,
Expand Down Expand Up @@ -262,26 +289,6 @@ var DOMRenderer = ReactFiberReconciler({
parentInstance.removeChild(child);
},

shouldFocusHostComponent(
type : string,
props : Props,
) : boolean {
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!(props : any).autoFocus;
}
return false;
},

focusHostComponent(
domElement : Instance,
) : void {
(domElement : any).focus();
},

scheduleAnimationCallback: window.requestAnimationFrame,

scheduleDeferredCallback: window.requestIdleCallback,
Expand Down
18 changes: 13 additions & 5 deletions src/renderers/native/ReactNativeFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ const NativeRenderer = ReactFiberReconciler({
);
},

commitInitialEffects(
instance : Instance,
type : string,
newProps : Props,
rootContainerInstance : Object,
internalInstanceHandle : Object
) : void {
// Noop
},

commitUpdate(
instance : Instance,
type : string,
Expand Down Expand Up @@ -197,7 +207,7 @@ const NativeRenderer = ReactFiberReconciler({
type : string,
props : Props,
rootContainerInstance : Container,
) : void {
) : boolean {
// Map from child objects to native tags.
// Either way we need to pass a copy of the Array to prevent it from being frozen.
const nativeTags = parentInstance._children.map(
Expand All @@ -210,6 +220,8 @@ const NativeRenderer = ReactFiberReconciler({
parentInstance._nativeTag, // containerTag
nativeTags // reactTags
);

return false;
},

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

scheduleDeferredCallback: global.requestIdleCallback,

shouldFocusHostComponent(type : string, props : Props) : boolean {
return false;
},

shouldSetTextContent(props : Props) : boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
Expand Down
15 changes: 6 additions & 9 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ var NoopRenderer = ReactFiberReconciler({
parentInstance.children.push(child);
},

finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
// Noop
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : boolean {
return false;
},

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

commitInitialEffects(instance : Instance, type : string, newProps : Props) : void {
// Noop
},

commitUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : void {
instance.prop = newProps.prop;
},
Expand Down Expand Up @@ -165,13 +169,6 @@ var NoopRenderer = ReactFiberReconciler({
resetAfterCommit() : void {
},

shouldFocusHostComponent(type : string, props : Props) : boolean {
return false;
},

focusHostComponent(instance : Instance) : void {
},

});

var rootContainers = new Map();
Expand Down
17 changes: 7 additions & 10 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var {
Fragment,
} = ReactTypeOfWork;
var {
Focus,
Update,
} = ReactTypeOfSideEffect;

Expand All @@ -57,7 +56,6 @@ module.exports = function<T, P, I, TI, C, CX>(
appendInitialChild,
finalizeInitialChildren,
prepareUpdate,
shouldFocusHostComponent,
} = config;

const {
Expand Down Expand Up @@ -242,15 +240,14 @@ module.exports = function<T, P, I, TI, C, CX>(
workInProgress
);

// Certain renderers (eg DOM) support focusable components.
// It is not safe to focus components until they have been mounted though.
// For now we should just mark them for later work.
if (shouldFocusHostComponent(type, newProps)) {
workInProgress.effectTag |= Focus;
}

appendAllChildren(instance, workInProgress);
finalizeInitialChildren(instance, type, newProps, rootContainerInstance);

// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
workInProgress.effectTag |= Update;
}

workInProgress.stateNode = instance;
if (workInProgress.ref) {
Expand Down
6 changes: 2 additions & 4 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ export type HostConfig<T, P, I, TI, C, CX> = {

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

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

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

useSyncScheduling ?: boolean,

shouldFocusHostComponent(type : T, props : P) : boolean,
focusHostComponent(instance : I) : void,
};

export type Reconciler<C, I, TI> = {
Expand Down
23 changes: 18 additions & 5 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ var {
} = require('ReactPriorityLevel');

var {
Focus,
NoEffect,
Placement,
Update,
Expand Down Expand Up @@ -76,7 +75,12 @@ var timeHeuristicForUnitOfWork = 1;

module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
const hostContext = ReactFiberHostContext(config);
const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
const {
getRootHostContainer,
popHostContainer,
popHostContext,
resetHostContainer,
} = hostContext;
const { beginWork, beginFailedWork } = ReactFiberBeginWork(
config,
hostContext,
Expand All @@ -96,7 +100,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
useSyncScheduling,
prepareForCommit,
resetAfterCommit,
focusHostComponent,
commitInitialEffects,
} = config;

// The priority level to use when scheduling an update.
Expand Down Expand Up @@ -289,8 +293,17 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
while (nextEffect) {
const current = nextEffect.alternate;

if (nextEffect.effectTag & Focus) {
focusHostComponent(nextEffect.stateNode);
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
if (
!current &&
nextEffect.effectTag & Update
) {
const type = nextEffect.type;
const props = nextEffect.memoizedProps;
const instance : I = nextEffect.stateNode;
const rootContainerInstance = getRootHostContainer();
commitInitialEffects(instance, type, props, rootContainerInstance, nextEffect);
}

// Use Task priority for lifecycle updates
Expand Down
19 changes: 9 additions & 10 deletions src/renderers/shared/fiber/ReactTypeOfSideEffect.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@

'use strict';

export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 16 | 32 | 64;
export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 16 | 32;

module.exports = {
NoEffect: 0, // 0b0000000
Placement: 1, // 0b0000001
Update: 2, // 0b0000010
PlacementAndUpdate: 3, // 0b0000011
Deletion: 4, // 0b0000100
ContentReset: 8, // 0b0001000
Callback: 16, // 0b0010000
Focus: 32, // 0b0100000
Err: 64, // 0b1000000
NoEffect: 0, // 0b000000
Placement: 1, // 0b000001
Update: 2, // 0b000010
PlacementAndUpdate: 3, // 0b000011
Deletion: 4, // 0b000100
ContentReset: 8, // 0b001000
Callback: 16, // 0b010000
Err: 32, // 0b100000
};

0 comments on commit d2dd297

Please sign in to comment.