Keep autoFocus attribute in the DOM (#11192)

* Keep autoFocus attribute in the DOM

* Don't emit autoFocus attribute on the client

* Test that hydration doesn't call focus

* Add autoFocus to SSR fixture
This commit is contained in:
Dan Abramov 2017-10-11 18:52:12 +01:00 committed by GitHub
parent b75be6a363
commit 2228f497f3
5 changed files with 39 additions and 1 deletions

View File

@ -2,6 +2,11 @@ import React, {Component} from 'react';
import './Page.css';
const autofocusedInputs = [
<input key="0" autoFocus placeholder="Has auto focus" />,
<input key="1" autoFocus placeholder="Has auto focus" />,
];
export default class Page extends Component {
state = {active: false};
handleClick = e => {
@ -18,9 +23,16 @@ export default class Page extends Component {
<p suppressHydrationWarning={true}>
A random number: {Math.random()}
</p>
<p>
Autofocus on page load: {autofocusedInputs}
</p>
<p>
{!this.state.active ? link : 'Thanks!'}
</p>
{this.state.active &&
<p>
Autofocus on update: {autofocusedInputs}
</p>}
</div>
);
}

View File

@ -54,6 +54,7 @@ var registrationNameModules = EventPluginRegistry.registrationNameModules;
var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
var AUTOFOCUS = 'autoFocus';
var CHILDREN = 'children';
var STYLE = 'style';
var HTML = '__html';
@ -286,6 +287,9 @@ function setInitialDOMProperties(
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// We polyfill it separately on the client during commit.
// We blacklist it here rather than in the property list because we emit it in SSR.
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
@ -681,6 +685,8 @@ var ReactDOMFiberComponent = {
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// Noop. It doesn't work on updates anyway.
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
// that the "current" fiber pointer gets updated so we need a commit

View File

@ -16,7 +16,6 @@ var invariant = require('fbjs/lib/invariant');
var RESERVED_PROPS = {
children: true,
dangerouslySetInnerHTML: true,
autoFocus: true,
defaultValue: true,
defaultChecked: true,
innerHTML: true,

View File

@ -26,6 +26,7 @@ var HTMLDOMPropertyConfig = {
// name warnings.
Properties: {
allowFullScreen: HAS_BOOLEAN_VALUE,
autoFocus: HAS_STRING_BOOLEAN_VALUE,
// specifies target context for links with `preload` type
async: HAS_BOOLEAN_VALUE,
// autoFocus is polyfilled/normalized by AutoFocusUtils

View File

@ -351,6 +351,26 @@ describe('ReactDOMServer', () => {
expect(numClicks).toEqual(2);
});
// We have a polyfill for autoFocus on the client, but we intentionally don't
// want it to call focus() when hydrating because this can mess up existing
// focus before the JS has loaded.
it('should emit autofocus on the server but not focus() when hydrating', () => {
var element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<input autoFocus={true} />,
);
expect(element.firstChild.autofocus).toBe(true);
// It should not be called on mount.
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
// Or during an update.
ReactDOM.render(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
});
it('should throw with silly args', () => {
expect(
ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),