Is this a Bug in React, Chromium, or the HTML Spec?

Published on August 23, 2019 • 3 min read

I came across what I thought to be an interesting bug in React, only to realize it was really a bug in Chromium, only to realize it was really a bug in the HTML spec itself. When you have an HTML <input> with a type="number" and you're using React to set the input's value, the wrong value gets passed to onChange() if it contains e. Click the Result tab below and type in the number 2e2 - be sure to pay close attention to the value label as you're typing it in. (note that 2e2 = 2*10^2 = 200)

Did you catch it? state.value was set to an empty string right when the input was 2e. Why did React's event handler fire with an empty string? Surely that must be a bug in React, right? The input's value changed, and the wrong input value was passed to the onChange function.

Surprisingly, this behavior is expected. React does indeed call the event handler with an empty string, however it's calling onChange with Chromium's value of the input. At the moment the input contains the string 2e, Chromium's value attribute is "". React is simply accessing the <input>'s value attribute via Chromium, so React's behavior is normal. The real question is why does Chromium consider 2e to be an empty string?

There's an interesting Chromium bug report that explains the behavior. The HTML spec says that the value of an <input type="number"> must evaluate to either an empty string, or a valid floating point number.

Here's the HTML spec definition for <input type="number">:

"The value attribute, if specified and not empty, must have a value that is a valid floating-point number.

The value sanitization algorithm is as follows: If the value of the element is not a valid floating-point number, then set it to the empty string instead."

Well, that makes sense. Chrome is simply implementing the HTML spec for a number input by setting its value attribute to an empty string when the value is an invalid floating-point number, as is the case with 2e. React is also in the clear because they're just using the Chromium API to access the input's value when calling an event handler. So, does this mean the problem is with the HTML spec itself? Here's a post from the Chromium bug report:

"The standard defines this behavior.  Please raise an issue to the standard body if you are not satisfied with this behavior."

Chrome's job is to implement the HTML spec, and React simply gets the input's value via the Chrome API. While I don't think this is necessarily a bug in the HTML spec per se, I think there should be more predictable behavior for number inputs. It looks like we finally have a reason to be excited for HTML6!

Back to the original problem: how can we define an <input type="number"> in React without having to worry about users typing in that pesky letter e and setting inaccurate state? Ignoring the letter e in the event handler won't work because there's no way to differentiate between an invalid floating-point number and an empty string.

Here's my solution.

By setting <input type="text">, we're allowed to enter any characters and React will always pass an accurate value to the event handler. However, mobile browsers often show a numerical keyboard when there's a number input, and using a text input will bring the default keyboard up. To force-show the numerical keyboard, we can set the input's inputmode="numeric". The inputmode attribute defines what type of data might be entered by the user. In the event handler, we use a simply regex check to only allow the input's value to contain digits. This removes e as well as + and - from ever appearing in our better number input field. This fix should be good enough in 99% of numerical input use cases, and can easily be extended to support exponents, decimal, and negative values by changing the regex.