React controlled input cursor jumps React controlled input cursor jumps reactjs reactjs

React controlled input cursor jumps


Taking a guess by your question, your code most likely looks similar to this:

    <input        autoFocus="autofocus"        type="text"        value={this.state.value}        onChange={(e) => this.setState({value: e.target.value})}    />

This may vary in behaviour if your event is handled with onBlur but essentially its the same issue. The behaviour here, which many have stated as a React "bug", is actually expected behaviour.

Your input control's value is not an initial value of the control when its loaded, but rather an underlying value bound to this.state. And when the state changes the control is re-rendered by React.

Essentially this means that the control is recreated by React and populated by the state's value. The problem is that it has no way of knowing what the cursor position was before it was recreated.

One way of solving this which I found to work is remembering the cursor position before it was re-rendered as follows:

    <input        autoFocus="autofocus"        type="text"        value={this.state.value}        onChange={(e) => {            this.cursor = e.target.selectionStart;            this.setState({value: e.target.value});        }}        onFocus={(e) => {            e.target.selectionStart = this.cursor;        }}    />


This is my solution:

import React, { Component } from "react";class App extends Component {  constructor(props) {    super(props);    this.state = {      name: ""    };    //get reference for input    this.nameRef = React.createRef();    //Setup cursor position for input    this.cursor;  }  componentDidUpdate() {    this._setCursorPositions();  }  _setCursorPositions = () => {    //reset the cursor position for input    this.nameRef.current.selectionStart = this.cursor;    this.nameRef.current.selectionEnd = this.cursor;  };  handleInputChange = (key, val) => {    this.setState({      [key]: val    });  };  render() {    return (      <div className="content">        <div className="form-group col-md-3">          <label htmlFor="name">Name</label>          <input            ref={this.nameRef}            type="text"            autoComplete="off"            className="form-control"            id="name"            value={this.state.name}            onChange={event => {              this.cursor = event.target.selectionStart;              this.handleInputChange("name", event.currentTarget.value);            }}          />        </div>      </div>    );  }}export default App;


This is an easy solution. Worked for me.

<Inputref={input=>input && (input.input.selectionStart=input.input.selectionEnd=this.cursor)}value={this.state.inputtext} onChange={(e)=>{this.cursor = e.target.selectionStart;this.setState({inputtext: e.target.value})/>

Explanation:

What we are doing here is we save the cursor position in onChange(), now when the tag re-renders due to a change in the state value, the ref code is executed, and inside the ref code we restore out cursor position.