Skip to content

Commit

Permalink
Do not assign node.value on input creation if no change will occur
Browse files Browse the repository at this point in the history
This commit fixes an issue where assigning an empty string to required
text inputs triggers the invalid state in Firefox (~60.0.1).

It does this by first comparing the initial state value to the current
value property on the text element. This:

1. Prevents the validation issue
2. Avoids an extra DOM Mutation in some cases
  • Loading branch information
nhunzaker committed May 29, 2018
1 parent aa85b0f commit b864343
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 72 deletions.
21 changes: 18 additions & 3 deletions fixtures/dom/src/components/fixtures/text-inputs/index.js
Expand Up @@ -64,21 +64,36 @@ class TextInputFixtures extends React.Component {
<Fixture>
<form className="control-box">
<fieldset>
<legend>Text</legend>
<legend>Empty value prop string</legend>
<input value="" required={true} />
</fieldset>
<fieldset>
<legend>No value prop</legend>
<input required={true} />
</fieldset>
<fieldset>
<legend>Date</legend>
<legend>Empty defaultValue prop string</legend>
<input required={true} defaultValue="" />
</fieldset>
<fieldset>
<legend>No value prop date input</legend>
<input type="date" required={true} />
</fieldset>
<fieldset>
<legend>Empty value prop date input</legend>
<input type="date" value="" required={true} />
</fieldset>
</form>
</Fixture>

<p className="footnote">
Checking the date type is also important because of a prior fix for
iOS Safari that involved assigning over value/defaultValue
properties of the input to prevent a display bug. This also
triggered input validation.
triggers input validation.
</p>
<p className="footnote">
The date inputs should be blank in iOS. This is not a bug.
</p>
</TestCase>

Expand Down
14 changes: 11 additions & 3 deletions packages/react-dom/src/client/ReactDOMFiberInput.js
Expand Up @@ -209,16 +209,24 @@ export function postMountWrapper(element: Element, props: Object) {
const node = ((element: any): InputWithWrapperState);

if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {
const initialValue = '' + node._wrapperState.initialValue;
const currentValue = node.value;

// Do not assign value if it is already set. This prevents user text input
// from being lost during SSR hydration.
if (node.value === '') {
node.value = '' + node._wrapperState.initialValue;
if (currentValue === '') {
// Do not re-assign the value property if there is no change. This
// potentially avoids a DOM write and prevents Firefox (~60.0.1) from
// prematurely marking required inputs as invalid
if (initialValue !== currentValue) {
node.value = initialValue;
}
}

// value must be assigned before defaultValue. This fixes an issue where the
// visually displayed value of date inputs disappears on mobile Safari and Chrome:
// https://github.com/facebook/react/issues/7233
node.defaultValue = '' + node._wrapperState.initialValue;
node.defaultValue = initialValue;
}

// Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
Expand Down
132 changes: 66 additions & 66 deletions scripts/rollup/results.json
Expand Up @@ -46,29 +46,29 @@
"filename": "react-dom.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 636515,
"gzip": 148265
"size": 639265,
"gzip": 148740
},
{
"filename": "react-dom.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 95960,
"gzip": 31067
"size": 96122,
"gzip": 31112
},
{
"filename": "react-dom.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 620506,
"gzip": 144162
"size": 623256,
"gzip": 144651
},
{
"filename": "react-dom.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 94422,
"gzip": 30018
"size": 94587,
"gzip": 30076
},
{
"filename": "ReactDOM-dev.js",
Expand Down Expand Up @@ -221,29 +221,29 @@
"filename": "react-art.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-art",
"size": 413567,
"gzip": 92395
"size": 416004,
"gzip": 92806
},
{
"filename": "react-art.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-art",
"size": 82648,
"gzip": 25481
"size": 82821,
"gzip": 25516
},
{
"filename": "react-art.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-art",
"size": 337640,
"gzip": 73051
"size": 340077,
"gzip": 73406
},
{
"filename": "react-art.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-art",
"size": 46153,
"gzip": 14348
"size": 46325,
"gzip": 14365
},
{
"filename": "ReactART-dev.js",
Expand Down Expand Up @@ -291,29 +291,29 @@
"filename": "react-test-renderer.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-test-renderer",
"size": 347431,
"gzip": 75093
"size": 348566,
"gzip": 75374
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-test-renderer",
"size": 46664,
"gzip": 14400
"size": 46908,
"gzip": 14528
},
{
"filename": "react-test-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
"size": 338042,
"gzip": 72285
"size": 339177,
"gzip": 72575
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-test-renderer",
"size": 45745,
"gzip": 13915
"size": 45991,
"gzip": 14065
},
{
"filename": "ReactTestRenderer-dev.js",
Expand Down Expand Up @@ -375,29 +375,29 @@
"filename": "react-reconciler.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 331575,
"gzip": 70231
"size": 332072,
"gzip": 70324
},
{
"filename": "react-reconciler.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
"size": 46245,
"gzip": 13814
"size": 46262,
"gzip": 13819
},
{
"filename": "react-reconciler-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 330095,
"gzip": 69615
"size": 330592,
"gzip": 69703
},
{
"filename": "react-reconciler-persistent.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
"size": 46256,
"gzip": 13819
"size": 46273,
"gzip": 13824
},
{
"filename": "react-reconciler-reflection.development.js",
Expand Down Expand Up @@ -515,15 +515,15 @@
"filename": "ReactDOM-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
"size": 643334,
"gzip": 146983
"size": 646066,
"gzip": 147453
},
{
"filename": "ReactDOM-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
"size": 280306,
"gzip": 52771
"size": 281070,
"gzip": 52902
},
{
"filename": "ReactTestUtils-dev.js",
Expand Down Expand Up @@ -564,78 +564,78 @@
"filename": "ReactART-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-art",
"size": 343294,
"gzip": 72413
"size": 345713,
"gzip": 72778
},
{
"filename": "ReactART-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-art",
"size": 150369,
"gzip": 25956
"size": 151089,
"gzip": 26031
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
"size": 468415,
"gzip": 102416
"size": 468774,
"gzip": 102457
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 210769,
"gzip": 36828
"size": 210616,
"gzip": 36800
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
"size": 468069,
"gzip": 102344
"size": 468428,
"gzip": 102391
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
"size": 198322,
"gzip": 34717
"size": 198313,
"gzip": 34718
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
"size": 459114,
"gzip": 100116
"size": 459473,
"gzip": 100166
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
"size": 190386,
"gzip": 33327
"size": 190377,
"gzip": 33325
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
"size": 459151,
"gzip": 100132
"size": 459510,
"gzip": 100184
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
"size": 190422,
"gzip": 33346
"size": 190413,
"gzip": 33343
},
{
"filename": "ReactTestRenderer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-test-renderer",
"size": 344028,
"gzip": 71858
"size": 345219,
"gzip": 72155
},
{
"filename": "ReactShallowRenderer-dev.js",
Expand All @@ -662,29 +662,29 @@
"filename": "react-scheduler.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-scheduler",
"size": 14094,
"gzip": 4681
"size": 16042,
"gzip": 4949
},
{
"filename": "react-scheduler.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-scheduler",
"size": 2142,
"gzip": 1068
"size": 2323,
"gzip": 1100
},
{
"filename": "react-scheduler.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-scheduler",
"size": 13898,
"gzip": 4632
"size": 15846,
"gzip": 4900
},
{
"filename": "react-scheduler.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-scheduler",
"size": 2230,
"gzip": 1097
"size": 2417,
"gzip": 1121
},
{
"filename": "SimpleCacheProvider-dev.js",
Expand Down

0 comments on commit b864343

Please sign in to comment.