Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Losing control of <input type='number' /> #1549

Closed
Enome opened this issue May 17, 2014 · 27 comments
Closed

Losing control of <input type='number' /> #1549

Enome opened this issue May 17, 2014 · 27 comments

Comments

@Enome
Copy link
Contributor

Enome commented May 17, 2014

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.

@syranide
Copy link
Contributor

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.

@Enome
Copy link
Contributor Author

Enome commented May 17, 2014

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

@jefffriesen
Copy link

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.

@syranide
Copy link
Contributor

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

@gre
Copy link
Contributor

gre commented Jun 7, 2014

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

@syranide
Copy link
Contributor

syranide commented Jun 7, 2014

@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.

@gre
Copy link
Contributor

gre commented Jun 7, 2014

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" />

@syranide
Copy link
Contributor

syranide commented Jun 7, 2014

@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.

@gre
Copy link
Contributor

gre commented Jun 7, 2014

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

@syranide
Copy link
Contributor

syranide commented Jun 7, 2014

@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.

@markusk88
Copy link

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/

@blainekasten
Copy link
Contributor

blainekasten commented Mar 17, 2015

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.

@JeffOwOSun
Copy link

JeffOwOSun commented Aug 19, 2016

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

@donavon
Copy link

donavon commented Aug 20, 2016

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.

@sohibul
Copy link

sohibul commented Oct 28, 2016

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.

@ThomsCass
Copy link

+1

@msal98
Copy link

msal98 commented Dec 7, 2016

+1

2 similar comments
@brandensilva
Copy link

+1

@agraver
Copy link

agraver commented Dec 22, 2016

+1

@aweary
Copy link
Contributor

aweary commented Dec 28, 2016

Please stop adding "+1" comments.

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

@nhunzaker
Copy link
Contributor

@aweary I think I got this with:
#7359

@nhunzaker
Copy link
Contributor

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.

@CluEleSsUK
Copy link

CluEleSsUK commented Aug 23, 2019

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.

@nhunzaker
Copy link
Contributor

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

@CluEleSsUK
Copy link

Sure, will do!

@nikolaas
Copy link

nikolaas commented Feb 6, 2020

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

@dcods22
Copy link

dcods22 commented Feb 28, 2020

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 (
<NumInput
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;

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests