diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index e78188ac61a67..d1095150b2d44 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -620,6 +620,7 @@ src/renderers/dom/shared/__tests__/ReactDOM-test.js
* should purge the DOM cache when removing nodes
* allow React.DOM factories to be called without warnings
* preserves focus
+* calls focus() on autoFocus elements after they have been mounted to the DOM
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should handle className
diff --git a/src/renderers/art/ReactARTFiber.js b/src/renderers/art/ReactARTFiber.js
index 6244642a8432d..4c3f22ea056fa 100644
--- a/src/renderers/art/ReactARTFiber.js
+++ b/src/renderers/art/ReactARTFiber.js
@@ -414,6 +414,10 @@ const ARTRenderer = ReactFiberReconciler({
// Noop
},
+ commitMount(instance, type, newProps) {
+ // Noop
+ },
+
commitUpdate(instance, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
@@ -457,7 +461,7 @@ const ARTRenderer = ReactFiberReconciler({
},
finalizeInitialChildren(domElement, type, props) {
- // Noop
+ return false;
},
insertBefore(parentInstance, child, beforeChild) {
diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js
index 3b6330db66562..20d851dcee681 100644
--- a/src/renderers/dom/fiber/ReactDOMFiber.js
+++ b/src/renderers/dom/fiber/ReactDOMFiber.js
@@ -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 {
@@ -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(
@@ -197,6 +212,18 @@ var DOMRenderer = ReactFiberReconciler({
return true;
},
+ commitMount(
+ domElement : Instance,
+ type : string,
+ newProps : Props,
+ rootContainerInstance : Container,
+ internalInstanceHandle : Object,
+ ) : void {
+ if (shouldAutoFocusHostComponent(type, newProps)) {
+ (domElement : any).focus();
+ }
+ },
+
commitUpdate(
domElement : Instance,
type : string,
diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
index 49ed51cad9246..f583cfc5e0f49 100644
--- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js
+++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
@@ -27,7 +27,6 @@ var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea');
var { getCurrentFiberOwnerName } = require('ReactDebugCurrentFiber');
var emptyFunction = require('emptyFunction');
-var focusNode = require('focusNode');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var setInnerHTML = require('setInnerHTML');
@@ -605,36 +604,18 @@ var ReactDOMFiberComponent = {
isCustomComponentTag
);
- // TODO: All these autoFocus won't work because the component is not in the
- // DOM yet. We need a special effect to handle this.
switch (tag) {
case 'input':
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
inputValueTracking.trackNode((domElement : any));
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
- if (props.autoFocus) {
- focusNode(domElement);
- }
break;
case 'textarea':
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
inputValueTracking.trackNode((domElement : any));
ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
- if (props.autoFocus) {
- focusNode(domElement);
- }
- break;
- case 'select':
- if (props.autoFocus) {
- focusNode(domElement);
- }
- break;
- case 'button':
- if (props.autoFocus) {
- focusNode(domElement);
- }
break;
case 'option':
ReactDOMFiberOption.postMountWrapper(domElement, rawProps);
diff --git a/src/renderers/dom/shared/__tests__/ReactDOM-test.js b/src/renderers/dom/shared/__tests__/ReactDOM-test.js
index d9bbe6c106151..a569abd37fc2e 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOM-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOM-test.js
@@ -235,4 +235,37 @@ describe('ReactDOM', () => {
]);
document.body.removeChild(container);
});
+
+ it('calls focus() on autoFocus elements after they have been mounted to the DOM', () => {
+ const originalFocus = HTMLElement.prototype.focus;
+
+ try {
+ let focusedElement;
+ let inputFocusedAfterMount = false;
+
+ // This test needs to determine that focus is called after mount.
+ // Can't check document.activeElement because PhantomJS is too permissive;
+ // It doesn't require element to be in the DOM to be focused.
+ HTMLElement.prototype.focus = function() {
+ focusedElement = this;
+ inputFocusedAfterMount = !!this.parentNode;
+ };
+
+ const container = document.createElement('div');
+ document.body.appendChild(container);
+ ReactDOM.render(
+
+
Auto-focus Test
+
+
The above input should be focused after mount.
+
,
+ container,
+ );
+
+ expect(inputFocusedAfterMount).toBe(true);
+ expect(focusedElement.tagName).toBe('INPUT');
+ } finally {
+ HTMLElement.prototype.focus = originalFocus;
+ }
+ });
});
diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js
index 1ca5d6e4b19cc..2c0ce2573acaf 100644
--- a/src/renderers/native/ReactNativeFiber.js
+++ b/src/renderers/native/ReactNativeFiber.js
@@ -110,6 +110,16 @@ const NativeRenderer = ReactFiberReconciler({
);
},
+ commitMount(
+ instance : Instance,
+ type : string,
+ newProps : Props,
+ rootContainerInstance : Object,
+ internalInstanceHandle : Object
+ ) : void {
+ // Noop
+ },
+
commitUpdate(
instance : Instance,
type : string,
@@ -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(
@@ -210,6 +220,8 @@ const NativeRenderer = ReactFiberReconciler({
parentInstance._nativeTag, // containerTag
nativeTags // reactTags
);
+
+ return false;
},
getRootHostContext() {
diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js
index b668cc9d882fd..804aba156fdac 100644
--- a/src/renderers/noop/ReactNoop.js
+++ b/src/renderers/noop/ReactNoop.js
@@ -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;
},
+ commitMount(instance : Instance, type : string, newProps : Props) : void {
+ // Noop
+ },
+
commitUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : void {
instance.prop = newProps.prop;
},
diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js
index 879fcf2485ee7..bb56a4c78f3bd 100644
--- a/src/renderers/shared/fiber/ReactFiberCommitWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js
@@ -40,6 +40,7 @@ module.exports = function(
) {
const {
+ commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
@@ -434,6 +435,21 @@ module.exports = function(
case HostComponent: {
const instance : I = finishedWork.stateNode;
attachRef(current, finishedWork, instance);
+
+ // Renderers may schedule work to be done after host components are mounted
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
+ // These effects should only be committed when components are first mounted,
+ // aka when there is no current/alternate.
+ if (
+ !current &&
+ finishedWork.effectTag & Update
+ ) {
+ const type = finishedWork.type;
+ const props = finishedWork.memoizedProps;
+ const rootContainerInstance = getRootHostContainer();
+ commitMount(instance, type, props, rootContainerInstance, finishedWork);
+ }
+
return;
}
case HostText: {
diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
index c0f41ab4c872f..bffecb3c4690e 100644
--- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
@@ -239,8 +239,15 @@ module.exports = function(
currentHostContext,
workInProgress
);
+
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) {
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index e05e6681348c3..ff55cbddaa008 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -50,10 +50,11 @@ export type HostConfig = {
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,
+ commitMount(instance : I, type : T, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
shouldSetTextContent(props : P) : boolean,
resetTextContent(instance : I) : void,
diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js
index 5f203409f9e5e..63cea47ee4c1c 100644
--- a/src/renderers/shared/fiber/ReactFiberScheduler.js
+++ b/src/renderers/shared/fiber/ReactFiberScheduler.js
@@ -371,6 +371,7 @@ module.exports = function(config : HostConfig