mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[Fiber] Update input.defaultValue for type=number too (#36980)
We used to avoid updating .defaultValue until blur on number inputs because of a Chrome bug, but the relevant Chromium behavior was fixed in 2020 in M85 https://issues.chromium.org/issues/40124871 and likewise in WebKit https://bugs.webkit.org/show_bug.cgi?id=217156 so I think we can probably just remove this special-cased logic. We do still need some special logic for number inputs because it's the only type where we let users specify a non-string value that has multiple string representations and need to be careful not to clobber the .value if it has a slightly different string than what you'd get from casting a number to string. Fixes #29862. Tested with the number inputs fixture in current Chrome, Safari, and Firefox.
This commit is contained in:
+23
-26
@@ -13,7 +13,6 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur
|
||||
import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
|
||||
import {getToStringValue, toString} from './ToStringValue';
|
||||
import {track, trackHydrated, updateValueIfChanged} from './inputValueTracking';
|
||||
import getActiveElement from './getActiveElement';
|
||||
import {
|
||||
disableInputAttributeSyncing,
|
||||
enableHydrationChangeEvent,
|
||||
@@ -121,10 +120,13 @@ export function updateInput(
|
||||
if (value != null) {
|
||||
if (type === 'number') {
|
||||
if (
|
||||
// "" == 0, so a cleared field wouldn't otherwise be restored to 0.
|
||||
// $FlowFixMe[incompatible-type]
|
||||
// $FlowFixMe[invalid-compare]
|
||||
(value === 0 && node.value === '') ||
|
||||
// We explicitly want to coerce to number here if possible.
|
||||
// We explicitly want to coerce to number here if possible, so that
|
||||
// other spellings of the same number (e.g. "0.0" mid-edit) aren't
|
||||
// clobbered while the user types.
|
||||
// eslint-disable-next-line
|
||||
node.value != (value as any)
|
||||
) {
|
||||
@@ -144,7 +146,7 @@ export function updateInput(
|
||||
// whenever the defaultValue React prop has changed. When not present,
|
||||
// React does nothing
|
||||
if (defaultValue != null) {
|
||||
setDefaultValue(node, type, getToStringValue(defaultValue));
|
||||
setDefaultValue(node, getToStringValue(defaultValue));
|
||||
} else if (lastDefaultValue != null) {
|
||||
node.removeAttribute('value');
|
||||
}
|
||||
@@ -155,9 +157,22 @@ export function updateInput(
|
||||
// 2. The defaultValue React property
|
||||
// 3. Otherwise there should be no change
|
||||
if (value != null) {
|
||||
setDefaultValue(node, type, getToStringValue(value));
|
||||
if (
|
||||
type === 'number' &&
|
||||
// We explicitly want to coerce to number here if possible.
|
||||
// eslint-disable-next-line
|
||||
node.value == (value as any)
|
||||
) {
|
||||
// node.value may be a different spelling of the same number (e.g.
|
||||
// "0.0" for 0). Mirror what's displayed, like the value setter does.
|
||||
// Not redundant with the assignment above: browsers sanitize invalid
|
||||
// assigned values to "", in which case we sync the React value below.
|
||||
setDefaultValue(node, getToStringValue(node.value));
|
||||
} else {
|
||||
setDefaultValue(node, getToStringValue(value));
|
||||
}
|
||||
} else if (defaultValue != null) {
|
||||
setDefaultValue(node, type, getToStringValue(defaultValue));
|
||||
setDefaultValue(node, getToStringValue(defaultValue));
|
||||
} else if (lastDefaultValue != null) {
|
||||
node.removeAttribute('value');
|
||||
}
|
||||
@@ -462,26 +477,8 @@ export function restoreControlledInputState(element: Element, props: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
// In Chrome, assigning defaultValue to certain input types triggers input validation.
|
||||
// For number inputs, the display value loses trailing decimal points. For email inputs,
|
||||
// Chrome raises "The specified value <x> is not a valid email address".
|
||||
//
|
||||
// Here we check to see if the defaultValue has actually changed, avoiding these problems
|
||||
// when the user is inputting text
|
||||
//
|
||||
// https://github.com/facebook/react/issues/7253
|
||||
export function setDefaultValue(
|
||||
node: HTMLInputElement,
|
||||
type: ?string,
|
||||
value: ToStringValue,
|
||||
) {
|
||||
if (
|
||||
// Focused number inputs synchronize on blur. See ChangeEventPlugin.js
|
||||
type !== 'number' ||
|
||||
getActiveElement(node.ownerDocument) !== node
|
||||
) {
|
||||
if (node.defaultValue !== toString(value)) {
|
||||
node.defaultValue = toString(value);
|
||||
}
|
||||
function setDefaultValue(node: HTMLInputElement, value: ToStringValue) {
|
||||
if (node.defaultValue !== toString(value)) {
|
||||
node.defaultValue = toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@ import getEventTarget from '../getEventTarget';
|
||||
import isEventSupported from '../isEventSupported';
|
||||
import {getNodeFromInstance} from '../../client/ReactDOMComponentTree';
|
||||
import {updateValueIfChanged} from '../../client/inputValueTracking';
|
||||
import {setDefaultValue} from '../../client/ReactDOMInput';
|
||||
import {enqueueStateRestore} from '../ReactDOMControlledComponent';
|
||||
|
||||
import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
|
||||
import {batchedUpdates} from '../ReactDOMUpdateBatching';
|
||||
import {
|
||||
processDispatchQueue,
|
||||
@@ -260,20 +258,6 @@ function getTargetInstForInputOrChangeEvent(
|
||||
}
|
||||
}
|
||||
|
||||
function handleControlledInputBlur(node: HTMLInputElement, props: any) {
|
||||
if (node.type !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!disableInputAttributeSyncing) {
|
||||
const isControlled = props.value != null;
|
||||
if (isControlled) {
|
||||
// If controlled, assign the value attribute to the current value on blur
|
||||
setDefaultValue(node as any, 'number', (node as any).value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This plugin creates an `onChange` event that normalizes change events
|
||||
* across form elements. This event fires at a time when it's possible to
|
||||
@@ -330,15 +314,6 @@ function extractEvents(
|
||||
if (handleEventFunc) {
|
||||
handleEventFunc(domEventName, targetNode, targetInst);
|
||||
}
|
||||
|
||||
// When blurring, set the value attribute for number inputs
|
||||
if (domEventName === 'focusout' && targetInst) {
|
||||
// These props aren't necessarily the most current but we warn for changing
|
||||
// between controlled and uncontrolled, so it doesn't matter and the previous
|
||||
// code was also broken for changes.
|
||||
const props = targetInst.memoizedProps;
|
||||
handleControlledInputBlur(targetNode as any as HTMLInputElement, props);
|
||||
}
|
||||
}
|
||||
|
||||
export {registerEvents, extractEvents};
|
||||
|
||||
+2
-5
@@ -1035,9 +1035,6 @@ describe('ReactDOMInput', () => {
|
||||
expect(node.value).toBe('0.0');
|
||||
expect(node.hasAttribute('value')).toBe(false);
|
||||
} else {
|
||||
dispatchEventOnNode(node, 'blur');
|
||||
dispatchEventOnNode(node, 'focusout');
|
||||
|
||||
expect(node.value).toBe('0.0');
|
||||
expect(node.getAttribute('value')).toBe('0.0');
|
||||
}
|
||||
@@ -2664,7 +2661,7 @@ describe('ReactDOMInput', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not set the value attribute on number inputs if focused', async () => {
|
||||
it('sets the value attribute on number inputs even when focused', async () => {
|
||||
const Input = getTestInput();
|
||||
await act(() => {
|
||||
root.render(<Input type="number" value="1" />);
|
||||
@@ -2681,7 +2678,7 @@ describe('ReactDOMInput', () => {
|
||||
if (disableInputAttributeSyncing) {
|
||||
expect(node.hasAttribute('value')).toBe(false);
|
||||
} else {
|
||||
expect(node.getAttribute('value')).toBe('1');
|
||||
expect(node.getAttribute('value')).toBe('2');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user