Skip to content

Commit db190e0

Browse files
committed
Fix input tracking bug (#26627)
In 2019ddc, we changed to set .defaultValue before .value on updates. In some cases, setting .defaultValue causes .value to change, and since we only set .value if it has the wrong value, this resulted in us not assigning to .value, which resulted in inputValueTracking not knowing the right value. See new test added. My fix here is to (a) move the value setting back up first and (b) narrowing the fix in the aforementioned PR to newly remove the value attribute only if it defaultValue was previously present in props. The second half is necessary because for types where the value property and attribute are indelibly linked (hidden checkbox radio submit image reset button, i.e. spec modes default or default/on from https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default), we can't remove the value attribute after setting .value, because that will undo the assignment we just did! That is, not having (b) makes all of those types fail to handle updating props.value. This code is incredibly hard to think about but I think this is right (or at least, as right as the old code was) because we set .value here only if the nextProps.value != null, and we now remove defaultValue only if lastProps.defaultValue != null. These can't happen at the same time because we have long warned if value and defaultValue are simultaneously specified, and also if a component switches between controlled and uncontrolled. Also, it fixes the test in #26626. DiffTrain build for [b433c37](b433c37)
1 parent b679adf commit db190e0

15 files changed

Lines changed: 807 additions & 573 deletions

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2bfe4b246f58d1f8d357f984fba9a8aa1fa79c73
1+
b433c379d55d9684945217c7d375de1082a1abb8

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-40ddfb33";
30+
var ReactVersion = "18.3.0-www-modern-edf301ed";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-09c9197a";
72+
var ReactVersion = "18.3.0-www-classic-145c42eb";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;

compiled/facebook-www/ReactART-prod.modern.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9637,7 +9637,7 @@ var slice = Array.prototype.slice,
96379637
return null;
96389638
},
96399639
bundleType: 0,
9640-
version: "18.3.0-www-modern-342727fc",
9640+
version: "18.3.0-www-modern-294eaa58",
96419641
rendererPackageName: "react-art"
96429642
};
96439643
var internals$jscomp$inline_1317 = {
@@ -9668,7 +9668,7 @@ var internals$jscomp$inline_1317 = {
96689668
scheduleRoot: null,
96699669
setRefreshHandler: null,
96709670
getCurrentFiber: null,
9671-
reconcilerVersion: "18.3.0-www-modern-342727fc"
9671+
reconcilerVersion: "18.3.0-www-modern-294eaa58"
96729672
};
96739673
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
96749674
var hook$jscomp$inline_1318 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled/facebook-www/ReactDOM-dev.classic.js

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3779,19 +3779,40 @@ function updateInput(
37793779
element,
37803780
value,
37813781
defaultValue,
3782+
lastDefaultValue,
37823783
checked,
37833784
defaultChecked,
37843785
type
37853786
) {
37863787
var node = element;
37873788

3789+
if (value != null) {
3790+
if (type === "number") {
3791+
if (
3792+
// $FlowFixMe[incompatible-type]
3793+
(value === 0 && node.value === "") || // We explicitly want to coerce to number here if possible.
3794+
// eslint-disable-next-line
3795+
node.value != value
3796+
) {
3797+
node.value = toString(getToStringValue(value));
3798+
}
3799+
} else if (node.value !== toString(getToStringValue(value))) {
3800+
node.value = toString(getToStringValue(value));
3801+
}
3802+
} else if (type === "submit" || type === "reset") {
3803+
// Submit/reset inputs need the attribute removed completely to avoid
3804+
// blank-text buttons.
3805+
node.removeAttribute("value");
3806+
return;
3807+
}
3808+
37883809
if (disableInputAttributeSyncing) {
37893810
// When not syncing the value attribute, React only assigns a new value
37903811
// whenever the defaultValue React prop has changed. When not present,
37913812
// React does nothing
37923813
if (defaultValue != null) {
37933814
setDefaultValue(node, type, getToStringValue(defaultValue));
3794-
} else {
3815+
} else if (lastDefaultValue != null) {
37953816
node.removeAttribute("value");
37963817
}
37973818
} else {
@@ -3804,7 +3825,7 @@ function updateInput(
38043825
setDefaultValue(node, type, getToStringValue(value));
38053826
} else if (defaultValue != null) {
38063827
setDefaultValue(node, type, getToStringValue(defaultValue));
3807-
} else {
3828+
} else if (lastDefaultValue != null) {
38083829
node.removeAttribute("value");
38093830
}
38103831
}
@@ -3829,26 +3850,6 @@ function updateInput(
38293850
if (checked != null && node.checked !== !!checked) {
38303851
node.checked = checked;
38313852
}
3832-
3833-
if (value != null) {
3834-
if (type === "number") {
3835-
if (
3836-
// $FlowFixMe[incompatible-type]
3837-
(value === 0 && node.value === "") || // We explicitly want to coerce to number here if possible.
3838-
// eslint-disable-next-line
3839-
node.value != value
3840-
) {
3841-
node.value = toString(getToStringValue(value));
3842-
}
3843-
} else if (node.value !== toString(getToStringValue(value))) {
3844-
node.value = toString(getToStringValue(value));
3845-
}
3846-
} else if (type === "submit" || type === "reset") {
3847-
// Submit/reset inputs need the attribute removed completely to avoid
3848-
// blank-text buttons.
3849-
node.removeAttribute("value");
3850-
return;
3851-
}
38523853
}
38533854
function initInput(
38543855
element,
@@ -3974,6 +3975,7 @@ function restoreControlledInputState(element, props) {
39743975
rootNode,
39753976
props.value,
39763977
props.defaultValue,
3978+
props.defaultValue,
39773979
props.checked,
39783980
props.defaultChecked,
39793981
props.type
@@ -4029,6 +4031,7 @@ function restoreControlledInputState(element, props) {
40294031
otherNode,
40304032
otherProps.value,
40314033
otherProps.defaultValue,
4034+
otherProps.defaultValue,
40324035
otherProps.checked,
40334036
otherProps.defaultChecked,
40344037
otherProps.type
@@ -33736,7 +33739,7 @@ function createFiberRoot(
3373633739
return root;
3373733740
}
3373833741

33739-
var ReactVersion = "18.3.0-www-classic-ffb509af";
33742+
var ReactVersion = "18.3.0-www-classic-4a39e57d";
3374033743

3374133744
function createPortal$1(
3374233745
children,
@@ -39353,36 +39356,42 @@ function updateProperties(domElement, tag, lastProps, nextProps) {
3935339356
var type = null;
3935439357
var value = null;
3935539358
var defaultValue = null;
39359+
var lastDefaultValue = null;
3935639360
var checked = null;
3935739361
var defaultChecked = null;
3935839362

3935939363
for (var propKey in lastProps) {
3936039364
var lastProp = lastProps[propKey];
3936139365

39362-
if (
39363-
lastProps.hasOwnProperty(propKey) &&
39364-
lastProp != null &&
39365-
!nextProps.hasOwnProperty(propKey)
39366-
) {
39366+
if (lastProps.hasOwnProperty(propKey) && lastProp != null) {
3936739367
switch (propKey) {
3936839368
case "checked": {
39369-
var checkedValue = nextProps.defaultChecked;
39370-
var inputElement = domElement;
39371-
inputElement.checked =
39372-
!!checkedValue &&
39373-
typeof checkedValue !== "function" &&
39374-
checkedValue !== "symbol";
39369+
if (!nextProps.hasOwnProperty(propKey)) {
39370+
var checkedValue = nextProps.defaultChecked;
39371+
var inputElement = domElement;
39372+
inputElement.checked =
39373+
!!checkedValue &&
39374+
typeof checkedValue !== "function" &&
39375+
checkedValue !== "symbol";
39376+
}
39377+
3937539378
break;
3937639379
}
3937739380

3937839381
case "value": {
3937939382
// This is handled by updateWrapper below.
3938039383
break;
3938139384
}
39385+
39386+
case "defaultValue": {
39387+
lastDefaultValue = lastProp;
39388+
}
3938239389
// defaultChecked and defaultValue are ignored by setProp
39390+
// Fallthrough
3938339391

3938439392
default: {
39385-
setProp(domElement, tag, propKey, null, nextProps, lastProp);
39393+
if (!nextProps.hasOwnProperty(propKey))
39394+
setProp(domElement, tag, propKey, null, nextProps, lastProp);
3938639395
}
3938739396
}
3938839397
}
@@ -39551,6 +39560,7 @@ function updateProperties(domElement, tag, lastProps, nextProps) {
3955139560
domElement,
3955239561
value,
3955339562
defaultValue,
39563+
lastDefaultValue,
3955439564
checked,
3955539565
defaultChecked,
3955639566
type
@@ -39952,6 +39962,7 @@ function updatePropertiesWithDiff(
3995239962
var type = nextProps.type;
3995339963
var value = nextProps.value;
3995439964
var defaultValue = nextProps.defaultValue;
39965+
var lastDefaultValue = lastProps.defaultValue;
3995539966
var checked = nextProps.checked;
3995639967
var defaultChecked = nextProps.defaultChecked;
3995739968

@@ -40092,6 +40103,7 @@ function updatePropertiesWithDiff(
4009240103
domElement,
4009340104
value,
4009440105
defaultValue,
40106+
lastDefaultValue,
4009540107
checked,
4009640108
defaultChecked,
4009740109
type

0 commit comments

Comments
 (0)