6

I have a number input field that sets the new value through the state when the user is typing. This works fine. If I however add decimals to it (toFixed), the cursor will jump to the end of the input after typing only one digit. This happens when I, for example, delete what is there and type something else. If I instead add a digit between the first one and the decimal point, the cursor does not jump. Has anyone experienced this before, and what was the solution?

Fiddle to show the problem: https://jsfiddle.net/Inverness/k04yvq1u/

constructor() {
    super()

    this.state = {
      number: 1
    }

    this.handleInputChange = this.handleInputChange.bind(this)
  }

  handleInputChange(event) {
    console.log(event.target.value)
    this.setState({
      number: event.target.value
    })
  }

  render() {
    return (
      <div>
        <input
          type="number"
          value={ parseFloat(this.state.number).toFixed(2) }
          onChange={ this.handleInputChange }
          />
      </div>
    )
  }
Ellinor
  • 323
  • 3
  • 14
  • The `value` in the `` element should only contain what is in the state, so any changes to the user's entered value would be easier to make in the `onChange` function. – hypnagogia Aug 22 '19 at 21:19

4 Answers4

5

I've had success using the onBlur event handler to simply format the input field to the way you need it after the user has finished typing.

Adding a bit of info in the form field label that the input will be limited to 2 decimal spaces should help alleviate any UX issues with changing a value that the user has provided.

 constructor() {
    super()

    this.state = {
      number: 1
    }

    this.handleInputChange = this.handleInputChange.bind(this)
    this.formatInput = this.formatInput.bind(this)
  }

  handleInputChange(event) {
    console.log(event.target.value)
    this.setState({
      number: event.target.value
    })
  }

  formatInput() {
    const num = this.state.number
    this.setState({
        number: parseFloat(num).toFixed(2)
    })
  }

  render() {
    return (
      <div>
        <input
          type="number"
          value={ this.state.number }
          onChange={ this.handleInputChange }
          onBlur={ this.formatInput }
          />
      </div>
    )
 }

JSFiddle

Michael Abeln
  • 2,518
  • 10
  • 20
1

I had something similar to this happen. The main issue here is that you are re-formatting what they type in as they type. Since react is using javascript to change the value, the browser can't guess where the cursor is supposed to go, so it just jumps to the end. The way I solved this was by only updating when the user enters something that matches the toFixed format. Something roughly equivalent that should work is setting value={this.state.number} on your input. Then the on change should look something like this:

handleInputChange = e => {
  let val = e.target.value
  if (/^[\d]*\.?[\d]{0,2}$/.test(val)) {
    this.setState({number: val})
  }
}

That way the input value still is constrained to be a two-decimal floating point format, but the user has more freedom to type it.

ryan28561
  • 302
  • 2
  • 11
0

The answer of @ryan28561 is right but the solution is not.

As he says, javascript won't know where the cursor should be, so what I recommend is the following:

Leave the input without parsing and instead, put a style or a warning to the input so that the user realizes that his input is not right. Besides, before using the number to any task, you can format it.

class TodoApp extends React.Component {
  constructor() {
    super()

    this.state = {
      number: '1',
      wrongInput: false, 
    }

    this.handleInputChange = this.handleInputChange.bind(this)
  }

  handleInputChange(event) {
    let val = event.target.value
    if (/^[\d]*\.?[\d]{0,2}$/.test(val)) {
        this.setState({number: val, wrongInput: false})
    } else {
        this.setState({number: val, wrongInput: true})
    }
  }

  render() {
    return (
      <div>
        <input
          //type="number"
          style={this.state.wrongInput ? { border: `solid 1px red`, outline: 'none'} : {}}
          value={this.state.number}
          onChange={ this.handleInputChange }
          />
      </div>
    )
  }
}
-1

Your toFixed function is changing the input value, so your input loses its cursor, as mentioned in the other answer I would suggest to avoid changing your input values, you can also try using a step attribute to have more control of your input.

<input
    type="number"
    step="0.01"
    value={ this.state.number }
    onChange={ this.handleInputChange }
    defaultValue={ parseFloat(this.state.number).toFixed(2) }
/>
joseluismurillorios
  • 1,005
  • 6
  • 11