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
Dan Abramov committed
Oct 11, 2017 at 18:52 UTC
2228f497f34e623367796a3db03d6582b36fb2f9
5 files changed
+39
-1
fixtures/ssr/src/components/Page.js
+12
@@ -2,6 +2,11 @@ import React, {Component} from 'react';
2
3
import './Page.css';
4
5
+const autofocusedInputs = [
6
+ <input key="0" autoFocus placeholder="Has auto focus" />,
7
+ <input key="1" autoFocus placeholder="Has auto focus" />,
8
+];
9
+
10
export default class Page extends Component {
11
state = {active: false};
12
handleClick = e => {
@@ -18,9 +23,16 @@ export default class Page extends Component {
23
<p suppressHydrationWarning={true}>
24
A random number: {Math.random()}
25
</p>
26
+ <p>
27
+ Autofocus on page load: {autofocusedInputs}
28
+ </p>
29
<p>
30
{!this.state.active ? link : 'Thanks!'}
31
</p>
32
+ {this.state.active &&
33
+ <p>
34
+ Autofocus on update: {autofocusedInputs}
35
+ </p>}
36
</div>
37
);
38
}
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+6
@@ -54,6 +54,7 @@ var registrationNameModules = EventPluginRegistry.registrationNameModules;
54
var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
55
var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
56
var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
57
+var AUTOFOCUS = 'autoFocus';
58
var CHILDREN = 'children';
59
var STYLE = 'style';
60
var HTML = '__html';
@@ -286,6 +287,9 @@ function setInitialDOMProperties(
287
propKey === SUPPRESS_HYDRATION_WARNING
288
) {
289
// Noop
290
+ } else if (propKey === AUTOFOCUS) {
291
+ // We polyfill it separately on the client during commit.
292
+ // We blacklist it here rather than in the property list because we emit it in SSR.
293
} else if (registrationNameModules.hasOwnProperty(propKey)) {
294
if (nextProp != null) {
295
if (__DEV__ && typeof nextProp !== 'function') {
@@ -681,6 +685,8 @@ var ReactDOMFiberComponent = {
685
propKey === SUPPRESS_HYDRATION_WARNING
686
) {
687
// Noop
688
+ } else if (propKey === AUTOFOCUS) {
689
+ // Noop. It doesn't work on updates anyway.
690
} else if (registrationNameModules.hasOwnProperty(propKey)) {
691
// This is a special case. If any listener updates we need to ensure
692
// that the "current" fiber pointer gets updated so we need a commit
src/renderers/dom/shared/DOMProperty.js
-1
@@ -16,7 +16,6 @@ var invariant = require('fbjs/lib/invariant');
16
var RESERVED_PROPS = {
17
children: true,
18
dangerouslySetInnerHTML: true,
19
- autoFocus: true,
19
defaultValue: true,
20
defaultChecked: true,
21
innerHTML: true,
src/renderers/dom/shared/HTMLDOMPropertyConfig.js
+1
@@ -26,6 +26,7 @@ var HTMLDOMPropertyConfig = {
26
// name warnings.
27
Properties: {
28
allowFullScreen: HAS_BOOLEAN_VALUE,
29
+ autoFocus: HAS_STRING_BOOLEAN_VALUE,
30
// specifies target context for links with `preload` type
31
async: HAS_BOOLEAN_VALUE,
32
// autoFocus is polyfilled/normalized by AutoFocusUtils
src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
+20
@@ -351,6 +351,26 @@ describe('ReactDOMServer', () => {
351
expect(numClicks).toEqual(2);
352
});
353
354
+ // We have a polyfill for autoFocus on the client, but we intentionally don't
355
+ // want it to call focus() when hydrating because this can mess up existing
356
+ // focus before the JS has loaded.
357
+ it('should emit autofocus on the server but not focus() when hydrating', () => {
358
+ var element = document.createElement('div');
359
+ element.innerHTML = ReactDOMServer.renderToString(
360
+ <input autoFocus={true} />,
361
+ );
362
+ expect(element.firstChild.autofocus).toBe(true);
363
+
364
+ // It should not be called on mount.
365
+ element.firstChild.focus = jest.fn();
366
+ ReactDOM.hydrate(<input autoFocus={true} />, element);
367
+ expect(element.firstChild.focus).not.toHaveBeenCalled();
368
+
369
+ // Or during an update.
370
+ ReactDOM.render(<input autoFocus={true} />, element);
371
+ expect(element.firstChild.focus).not.toHaveBeenCalled();
372
+ });
373
+
374
it('should throw with silly args', () => {
375
expect(
376
ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),