javascriptjqueryreactjs

Change the cursor position in a textarea with React


I have a textarea in React that I want to turn into a "notepad". Which means I want the "tab" key to indent instead of unfocus. I looked at this answer, but I can't get it to work with React. Here is my code:

handleKeyDown(event) {
    if (event.keyCode === 9) { // tab was pressed
        event.preventDefault();
        var val = this.state.scriptString,
            start = event.target.selectionStart,
            end = event.target.selectionEnd;

        this.setState({"scriptString": val.substring(0, start) + '\t' + val.substring(end)});
        // This line doesn't work. The caret position is always at the end of the line
        this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1;
    }
}
onScriptChange(event) {
   this.setState({scriptString: event.target.value});
}
render() {
    return (
        <textarea rows="30" cols="100" 
                  ref="input"
                  onKeyDown={this.handleKeyDown.bind(this)}
                  onChange={this.onScriptChange.bind(this)} 
                  value={this.state.scriptString}/>
    )
}

When I run this code, even if I press the "tab" key in the middle of the string, my cursor always appears at the end of the string instead. Anyone knows how to correctly set the cursor position?


Solution

  • You have to change the cursor position after the state has been updated(setState() does not immediately mutate this.state)

    In order to do that, you have to wrap this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1; in a function and pass it as the second argument to setState (callback).

    handleKeyDown(event) {
          if (event.keyCode === 9) { // tab was pressed
              event.preventDefault();
              var val = this.state.scriptString,
              start = event.target.selectionStart,
              end = event.target.selectionEnd;
              this.setState(
                  {
                      "scriptString": val.substring(0, start) + '\t' + val.substring(end)
                  },
                  () => {
                      this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1
                  });
          }
     }
    

    jsfiddle