When using number inputs with the step attribute for decimal values, I noticed that when I type "1.2" and then delete the "2" to leave "1.", the trailing decimal point remains visible in the input even after the input loses focus. The actual value is "1" (without the decimal), but the display still shows "1.".
Here is a React example but it happens in plain HTML input as well. How can I fix this visual inconsistency?
function Example() {
const [value, setValue] = React.useState('');
const [focused, setFocused] = React.useState(false);
return (
<div>
<label>
Enter a decimal number <br/>(try typing "1.2" then delete the "2" and then lose focus):
<br/><br/>
<input
type="number"
step="0.01"
value={value}
onChange={(e) => {
console.log("onChange value:", e.target.value);
setValue(e.target.value);
}}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={{padding: '5px', margin: '5px 0'}}
/>
</label>
<div style={{marginTop: '10px'}}>
<p><strong>Input is currently:</strong> {focused ? 'Focused' : 'Blurred'}</p>
<p><strong>Value in state:</strong> "{value}"</p>
</div>
</div>
);
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The answer works for most browser, but fails for e.g. Chrome Mobile for Android. The reason:
It looks like a trailing dot or comma isn't a valid number in Chrome Mobile for Android. AFAIK, there isn't much you can do with invalid text in a number input field. You can't access the raw text and the browser returns an empty string or NaN
for invalid input. Consider using a text input field instead if you have to support these browsers. valueAsNumber
returns NaN
and value
returns an empty string for input 1.
. See How to get the raw value an field?
This means that your code doesn't work in these browser even without my answer. The answer that works for most browsers:
You can remove the trailing decimal point the the onBlur
handler:
function Example() {
const [value, setValue] = React.useState('');
const [focused, setFocused] = React.useState(false);
return (
<div>
<label>
Enter a decimal number <br/>(try typing "1.2" then delete the "2" and then lose focus):
<br/><br/>
<input
type="number"
step="0.01"
value={value}
onChange={(e) => {
console.log("onChange value:", e.target.value);
setValue(e.target.value);
}}
onFocus={() => setFocused(true)}
onBlur={(e) => {e.target.valueAsNumber = e.target.valueAsNumber; return setFocused(false);}}
style={{padding: '5px', margin: '5px 0'}}
/>
</label>
<div style={{marginTop: '10px'}}>
<p><strong>Input is currently:</strong> {focused ? 'Focused' : 'Blurred'}</p>
<p><strong>Value in state:</strong> "{value}"</p>
</div>
</div>
);
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
A number input field only stores the numerical value, not the textual representation with trailing dot.
e.target.valueAsNumber
is a getter and a setter. When you read the value, you get the numerical value as number. When you set the value, the text in the input field is refreshed with the textual representation of the number without trailing dot.