4

In the React docs it says that in a controlled component, "the React state be the “single source of truth”". This seems to be true except in cases with leading zeros. In the below example, you can see that the input is controlled, but in a case with leading zeros, the input value doesn't always match the state value.


The Test

Type leading zeros, so the value is something like 0010.

You can see that the value in state is an integer 10, but the input shows 0010.

My first thought was this was because the component doesn't actually re-render, which is true. There's no state change between 10 and 10, so no new render. This is confirmed since we don't see the 'render' console.log firing.

So let's make sure we change the value. Add a 2 to change 0010 to 00102. The value in state is now 102, we confirm the component re-renders...but still the leading zeros persist in the input.


This seems inconsistent as I would expect the value in the input to always match the value in state exactly. Why is this and what is a reliable way to trim leading zeros from a number input?

// LEADING ZEROS INPUT TEST
// Step 1: type 0s into the input followed by any other digits
// Step 2: see that console log and val in `p` do not have leading 0's and input val does

const {useState} = React;

const App = () => {
  const [val, setVal] = useState(10);
  
  const handleChange = (evt) => {
    const strVal = evt.currentTarget.value;
    console.log('string', strVal, typeof strVal);
    const intVal = parseInt(strVal, 10) || 0; // || 0 bc '' returns NaN
    console.log('int', intVal, typeof intVal);
    setVal(intVal);
  }
  
  console.log('render', val);
  
  return (
    <div>
      <p>{val}</p>
      <input 
        type="number"
        value={val}
        onChange={handleChange}
      />
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
sallf
  • 2,583
  • 3
  • 19
  • 24
  • 1
    My wild guess is because inputs are strings, not integers, so it gets confused. E.g., if you keep `val` as a string, and convert it to a string after parsing/before `setVal`, it works (roughly) as expected, except for a wonky UX--which makes me thing that this kind of processing is best done later rather than on every key, since at least in Chrome it bounces the cursor to the end of the input if you enter a leading 0. – Dave Newton May 21 '21 at 17:41
  • As a sidenote. This code works as intended with classical `this.state/this.setState`. – aleksxor May 21 '21 at 17:43
  • 2
    Actually, this has been going on since a while with both sides of the debate making sense - https://github.com/facebook/react/issues/9402 – jaybhatt May 21 '21 at 17:52
  • 1
    @jaybhatt Ahhh. I was close, but wrong with why :) – Dave Newton May 21 '21 at 17:56
  • 1
    Tried to host a codepen to show how difficult this will be in terms of UX -https://codepen.io/jaybhatt21/pen/LYWZvbO. Like it removes trailing zero, but a very confusing experience. – jaybhatt May 21 '21 at 17:56
  • 1
    @jaybhatt That's what I did too, but it's pretty awful UX. – Dave Newton May 21 '21 at 17:58
  • Yeah, probably the only good way maybe to have it as `text` maybe. – jaybhatt May 21 '21 at 18:04
  • 1
    @DaveNewton thanks for the thought. Since in my real world use case I'm doing things like validating and limiting (min/max) the value, switching it to an integer and then back to a string before setting state just seems cumbersome. – sallf May 21 '21 at 18:09
  • @sallf It is. However: leading zeros don't affect validation or limits; they're just noise. I don't see a compelling reason to remove them at all, especially given the UX issues caused by the component re-render. – Dave Newton May 21 '21 at 18:12
  • @DaveNewton agreed, there's a good case to be made for "who cares, get over it" :). I guess what I'm more curious about is why is the input value different from the state value. I'm reading through the issue jaybhatt posted to see if I can make sense of it. – sallf May 21 '21 at 18:17
  • 2
    @sallf It's (er sort of) what I was saying, but for additional reasons, and changing that behavior leads to a significantly *worse* UX even than what I was seeing in my PoC, particularly for different varieties of floats. Another thing you *could* do is drop keyboard events when it's a 0 in the front--but I *really* wouldn't do that because it's just confusing. – Dave Newton May 21 '21 at 18:21
  • I agree with @DaveNewton if the user wants to put a freaking zero in front, why not? you can always show an error message "wrong input format" instead of messing with user interaction, is too intrusive. – Luis Sardon May 21 '21 at 18:24

1 Answers1

0

Since you are already aware of the WHY I'm going to help you with the HOW.

In my humble opinion a reliable way to trim leading zeros from a number input would be "on blur", this is less intrusive and provides a better user experience.

const {useState, useCallback} = React;

const App = () => {
  const [val, setVal] = useState(10);
  const [strVal, setStrVal] = useState("10"); // To keep a reference of the real input value
  
  const handleChange = (evt) => {
    const inputVal = evt.currentTarget.value;
    setStrVal(inputVal);
    setVal(parseInt(evt.currentTarget.value, 10) || 0);
  }

  const handleBlur = useCallback((evt) => {
    setStrVal(val.toString());
  }, [val]);
  
  return (
    <div>
      <p>{val}</p>
      <input
        type="number"
        value={strVal}
        onChange={handleChange}
        onBlur={handleBlur}
      />
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
Luis Sardon
  • 516
  • 3
  • 8
  • but is not what is OP asking for at all :) if you want to avoid user entering certain characters and we all would agree leading zeros for numeric input certainly are, especially e.g. if you want to allow positive intergers only, thus you need to filter what characters are entered – sKopheK Nov 03 '22 at 22:49