Измените положение курсора в текстовом поле с помощью React

У меня есть текстовое поле в React, которое я хочу превратить в "блокнот". Это означает, что я хочу, чтобы клавиша "tab" была отступом, а не сфокусирована. Я посмотрел на этот ответ, но я не могу заставить его работать с React. Вот мой код:

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}/>
    )
}

Когда я запускаю этот код, даже если я нажимаю клавишу "tab" в середине строки, мой курсор всегда появляется в конце строки. Кто-нибудь знает, как правильно установить позицию курсора?

Ответ 1

Вы должны изменить позицию курсора после обновления состояния (setState() не сразу мутирует this.state)

Для этого вам необходимо обернуть this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1; в функции и передать ее как второй аргумент setState (обратный вызов).

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

Ответ 2

Здесь решение в архитектуре стиля крючков. Моя рекомендация состоит в том, чтобы изменить value textarea и selectionStart немедленно при вставке вкладки.

import React, { useRef } from "react"

const CodeTextArea = ({ onChange, value, error }) => {
  const textArea = useRef()
  return (
      <textarea
        ref={textArea}
        onKeyDown={e => {
          if (e.key === "Tab") {
            e.preventDefault()

            const { selectionStart, selectionEnd } = e.target

            const newValue =
              value.substring(0, selectionStart) +
              "  " +
              value.substring(selectionEnd)

            onChange(newValue)
            if (textArea.current) {
              textArea.current.value = newValue
              textArea.current.selectionStart = textArea.current.selectionEnd =
                selectionStart + 2
            }
          }
        }}
        onChange={e => onChange(e.target.value)}
        value={value}
      />
  )
}