Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 23 additions & 26 deletions packages/react-dom-bindings/src/client/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
) {
Expand All @@ -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');
}
Expand All @@ -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');
}
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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};
7 changes: 2 additions & 5 deletions packages/react-dom/src/__tests__/ReactDOMInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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" />);
Expand All @@ -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');
}
});

Expand Down
Loading