React: Losing control of <input type='number' />

Created on 17 May 2014  ·  27Comments  ·  Source: facebook/react

http://jsfiddle.net/kb3gN/2498/

In the example above when you start typing numbers and then a non-number character it wont update the input field which is expected. If you start typing a non-number character first though it seems that the input field becomes uncontrolled and starts excepting anything you type and onChange isn't fired anymore.

Most helpful comment

Please stop adding "+1" comments.

If you want to support an issue, add a 👍 reaction to the original post.

All 27 comments

Yeah, I've noticed when experimenting with raw type="number" fields that once it's not plain numbers, the value becomes "". I'll just go ahead and recommend my personal solution; throw everything but <input type="text"> and <textarea> out the window and implement them yourself. Less browser weirdness and you get to style it yourself.

@syranide yeah, that's what I am planning. The down side is that I lose the numeric keyboard on mobile devices though.

For non-react apps, we've used this successfully:

<input type="text" pattern="[0-9]*" />

Here are some other options and some caveats: http://stackoverflow.com/questions/6178556/iphone-numeric-keyboard-for-text-input

I don't know if 'pattern' is supported by React though.

@jefffriesen It's supported :) Also, that's really good to know, thanks!

Hi guys, I'm also falling in this bug, I still want to use the native input number (native controls and change events), is there any way I can make this working again?

Is this because React preventDefault for all events?
Should I bind on the input and handling the DOM myself?

Thanks

@gre Sorry, this is literally a "bug" in the DOM (or Chrome at least), it's impossible to read the actual value of the input when it is non-numeric.

not sure to get what do you mean but

input.addEventListener("change", function (e) {
  var value = parseFloat(input.value, 10);
  ...
}, false);

works on Chrome.

as a proof I've used some here: https://glsl.io/transition/b6720916aa3f035949bc

<input type="number" step="0.1" />

@gre http://jsfiddle.net/44pnh/1/ try that in Chrome (it outputs below the input). As soon as you enter anything non-numeric, value becomes empty.

So as I understand this Chrome behavior make it impossible to implement the sync in React?

I don't know what says the spec, but I'm not sure to get (1) what is functionally wrong with that behaviour (it is a number input so non-number values doesn't make sense – but yeah maybe Chrome shouldn't allow inputing invalid text),
and (2) how is that related to the initial bug which is that React disables the arrow controls (both from keyboard and from the UI).

If this Chrome "bug" is fixed, there is still a bug in React making the input number working right?

Anyway, as you recommended previously, I'm gonna implement my own number component then ;-)
Thanks

@gre Don't know the specifics, but I guess it's the same as with auto-fill, the browser simply doesn't tell us when the user makes changes. If it's possible to listen to up/down then, yeah, seems reasonable that we should to it (but listening to KeyDown, MouseDown, etc to accomplish it is fragile).

PS. My point was basically that it's already kind of bad, if you implement it yourself you could easily even prevent invalid chars from being typed (you can for number too ofc). But like with selects, styling is always going to be an issue. Seemingly, there is also always going to be obscure bugs/behaviors.

A little late to the discussion, but I found an easy solution with some jquery magic.
If we modify the onChange to monitor for empty strings when the validity is false, we can manually set the input to value to ''

change: function (event) {
  if (event.target.validity.valid) {
      this.setState({ v: event.target.value });
  } else if (event.target.value == '') {
      $(this.getDOMNode()).val('');         
  }
}

http://jsfiddle.net/h6m3muhv/4/

Additionally, if you guys WANT the native numeric keyboard on mobile, you could wrap input in something like so:

class Input extends React.Component {
  render() {
    var type = navigator.userAgent.match(/Mobi/) && this.props.type === 'number' ? 'number' : 'text';

    return <input {...this.props} type={type} />
  }
}

or, react could do this if they dig the patch/hack.

Here's my solution.

handleUpdate(e) {
  if (e.target.validity.valid) {
    //input is numeric
    this.setState({inputValue: e.target.value});
  } else if (e.target.value == '') {
    //input is not numeric
    this.numberInput.value = ''; //suppress UI change
    this.setState({inputValue: ''}); //reset state
  }
}

render() {
  return (
    ...
    <input type="number" value={this.state.inputValue} onChange={handleUpdate} 
      ref={elm=>{this.numberInput=elm}}/>{/*use ref to expose underlying dom node*/}
    ...
  )
}

the idea is from @markusk88, but my version doesn't use jQuery

How about this (see pen). The key here is setting step to any to allow decimal numbers (if that's your intention) and reseting the target value to the last know valid value. You also don't need a ref because you already have the event object in the onClick handler which has a reference to the DOM node.

_handleUpdate(e) {
  if (!e.target.validity.valid) {
    e.target.value = this.state.inputValue; // Set to last known valid value.
  }
  this.setState({ inputValue: e.target.value }); 
}
<input type="number" value={this.state.inputValue} onChange={this._handleUpdate} step="any" />

The only side effect (as far as I've been able to detect) is that if on Safari and possibly FIrefox (untested), you type "1" then "2" then place the cursor between the "1" and the "2" (i.e. 1|2) and type a non-digit, the cursor will go to the end of your input (i.e. 12|). This too is avoidable, but IMO it's not worth the code complexity.

EDIT: I've shortened it to the following without consequence. It still works in Safari and without the "cursor to the end" issue I discribed above.

_handleUpdate(e) {
  if (e.target.validity.valid) {
    this.setState({ inputValue: e.target.value }); 
  }
}

EDIT: Although I see that is exactly the case of the original post, so now I'm confused as to the problem. It works in Chrome and Safari.

I have similar issue with this.

I can type ...1...111 in <input type="number" />

But if I has defaultValue <input type="number" defaultValue={10} />, i cannot type . (dot) first time. I tried type 1 then ., it failed then cursor go to the front (before 1). Then I can type ., so I have .1. But still I cannot type multiple . (dot), the field will be empty.

+1

+1

+1

+1

Please stop adding "+1" comments.

If you want to support an issue, add a 👍 reaction to the original post.

@aweary I think I got this with:
https://github.com/facebook/react/pull/7359

I believe that #7359 resolved this. If not, it definitely covers all of the cases I could find. We should move forward with new issues for particular edge cases if they are discovered.

I still have this problem; text typed in appears in the input, but the onChange listener is skipped and the controlled value does not change.
Once I type a number, the onChange listener is hit as usual.

@CluEleSsUK would it be possible to file a new issue with a reproducing example?

Sure, will do!

I have similar issue with this.

A controlled React input with type "number" clears its value when I add a dot to the end of the value.
Steps to reproduce:

  1. Click to an input
  2. Type "10"
  3. Click to another input
  4. Click to the first input
  5. Add "." to the end
  6. Click to another input

react-bug

See live demo here:

git clone https://github.com/nikolaas/controlled-react-number-input-bug.git
cd controlled-react-number-input-bug
npm install
npm start
open http://localhost:3000

The bug is reproduced in Google Chrome (80.0.3987.87, 64-bit).
In the same case, Safari (13.0.5) replaces the last dot with a comma, Firefox (62.0, 64-бит) allows adding a dot to the end of the value.

P.S. An uncontrolled React input with type "number" allows adding a dot to the end of the value in all browsers mentioned above. An input with type "number" in pure HTML (without React) has the same behavior.

Upd (2020-02-11)
React version: 16.12.0

HI,

Im having a similar issue where the onChange is not called in FireFox when the number input is not valid but the input maintains the value of the input and I cant get the change event to set the ref.current.value = '';

`
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import Input from "./Input";

const NumInput = styled(Input) -moz-appearance: textfield; ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { -webkit-appearance: none; appearance: none; } ;

const NumberInput = ({ name, value, onChange, disabled, innerRef, pattern, step, ...rest }) => {

const validateChange = event => {
if (event.target.value.length === 0 || event.target.validity.valid) {
onChange(event.target.value);
}
};

const validateOnBlur = event => {
let val = event.target.value;
if (val == null || val.trim() === "" || !event.target.validity.valid) {
onChange(val);
}
};

return (
id={name}
type="number"
pattern={pattern}
onChange={validateChange}
onBlur={validateOnBlur}
value={value}
disabled={disabled}
ref={innerRef}
step={step}
{...rest}
/>
);
};

NumberInput.propTypes = {
/* id property of the input field */
name: PropTypes.string,
/
* pattern of the input field to follow /
pattern: PropTypes.string,
/
* step of the input field to follow /
step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/
* property where the value of the input field will be stored /
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/
* function to be called when the input value is changed /
onChange: PropTypes.func.isRequired,
/
* flag to disable/enable the input field /
disabled: PropTypes.bool,
/
* reference to the number input */
innerRef: PropTypes.any
};

NumberInput.defaultProps = {
name: "best-number-input",
pattern: "[-+]?[0-9]*[.,]?[0-9]+",
step: "",
disabled: false,
innerRef: null,
value: null
};

export default NumberInput;

`

Was this page helpful?
0 / 5 - 0 ratings