2

Please, view my js fiddle where I've illustrated the problem

Here's my js fiddle: https://jsfiddle.net/jajabya/fb93f7b0/

My goad is to get an input tag where special words (like dates or users' names could be highlighted by wrapping in span tags)

There must be a problem with div, because when I use input field instead everything works fine.

My problem is that I can't make the caret appear in the right place Every time when the state updates in onInput

  onInput(event) {
    this.setState({
        html: event.target.innerText.toUpperCase()  
    });

  }

the caret rolls back to the beginning

Le garcon
  • 7,197
  • 9
  • 31
  • 46

1 Answers1

4

My idea is to save the current caret position in the state, and set it back via a ref in componentDidUpdate() (since ref doesn't rerender the component).

Note: This is a prototype idea, that I've never battle tested, so use with caution.

The caret position code was take from this answers:

  1. Code for getting the caret position

  2. Code for setting caret position

class Editable extends React.Component {
  componentDidUpdate(prev) {
    const { position } = this.props;
  
    if(position !== prev.position && this.ce.childNodes.length) {
      const range = document.createRange();
      const sel = window.getSelection();
      range.setStart(this.ce.childNodes[0], position);
      range.collapse(true);
      sel.removeAllRanges();
      sel.addRange(range);
    }
  }
  
  render() {
    return (
      <div
        contentEditable
        className={this.props.className}
        onInput={this.props.onInput}
        ref={ce => this.ce = ce}
        suppressContentEditableWarning>
        {this.props.html}
      </div>
    );
  }
}

class App extends React.Component {
  state = {
    html: 'Text',
    caret: 0
  };

  handleInput = (event) => this.setState({
    html: event.target.innerText.toUpperCase(),
    position: window.getSelection().getRangeAt(0).startOffset
  });
  
  render() {
    return (
     <Editable
        {...this.state}
        className="Editable"
        onInput={this.handleInput} />
    );
  }
}

ReactDOM.render(
  <App />,
  demo
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="demo"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209