React onChange text input is moving cursor to end of text in input field

Viewed 1811

I have a bug when I set the value from the state and change the value in a input.

when you write in the input and you try to correct the text the cursor move to the end.

This is my element

<div className="campo">
  <p>Nombre</p>
  <div className="campo-input">
    <input
     type="text"
     name="name"
     value={this.state.name}
     onChange={this.handleInputChangeUpperCase}
     />
  </div>
</div>

This is my function

handleInputChangeUpperCase = (e) => {
    let { name, value } = e.target;
    this.setState({ [name]: value.toUpperCase() });
  };

Working Snippet:

class App extends React.Component {
  state = {
    name: "Ronaldo",
  };
  
  // This is the function
  handleInputChangeUpperCase = (e) => {
    let { name, value } = e.target;
    this.setState({
      [name]: value.toUpperCase(),
    });
  };
  render() {
  
  // This is the Element
    return (
      <div className="campo">
        <p> Nombre </p>
        <div className="campo-input">
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.handleInputChangeUpperCase}
          />
          {this.state.name}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

4 Answers

To control where the cursor is in an input field, use selectionStart, which indicates where your selected text begins. As long as you don't set selectionEnd, you'll just move the cursor to the desired position.

For instance, let's move to the position "1", just after the first character. For input "123", that should be "1|23"...

document.getElementById('text-area').selectionStart = 1;
document.getElementById('text-area').focus()
  <input id="text-area" type="text" value="123"/>

For your situation, if you want the cursor to move to the start of the string, set .selectionStart = 0. If you want it to go back to where it was originally, save the value with var tempstart = ...selectionStart, and then restore it after you do the data change, .selectionStart = tempstart.

Source: Developer.Mozilla.org: HTMLInputElement.setSelectionRange()

Based on the original answer: https://stackoverflow.com/a/49648061/7427111

class App extends React.Component {
  state = {
    name: "Ronaldo",
  };
  
  handleInputChangeUpperCase = (e) => {
  
    // Here is the addition
    const pointer = e.target.selectionStart;
    const element = e.target;
    window.requestAnimationFrame(() => {
      element.selectionStart = pointer;
      element.selectionEnd = pointer;
    });

    let { name, value } = e.target;
    this.setState({
      [name]: value.toUpperCase(),
    });
  };
  render() {
    return (
      <div className="campo">
        <p> Nombre </p>
        <div className="campo-input">
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.handleInputChangeUpperCase}
          />
          {this.state.name}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

That behaviour seems to be happening with .toUpperCase();

handleInputChangeUpperCase = e => {    
    this.setState({ [e.target.name]: e.target.value });
};

If you don't require the text to be upper case immediately on input, I would apply the method outside of the setState

Related