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.