Caret position reverts to start of contenteditable span on re-render in React in Safari and Firefox
Asked Answered
D

2

22

When typing text into a contenteditable span in Safari or Firefox the caret moves to the start on each keypress. This only seems to happen when React re/renders the component.

Chrome works just fine. Haven't tested IE.


An example where | is the caret and typing Hello:

| |H |eH |leH |lleH |olleH

Here's a simplified version of my component:

import React, { Component } from 'react';

class ContentEditable extends Component {
    constructor(props) {
        super(props);

        this.state = {
            value: props.value
        };
    }

    createMarkup() {
        return { __html: this.state.value };
    }

    handleInput(event) {
        this.setState({
            value: event.target.innerText
        });
    }

    isValid() {
        let bestLength = 3;

        return this.state.value.length > bestLength;
    }

    render() {
        let className = '';
        if (this.isValid()) {
            className = 'is-valid';
        }

        return (
            <span
                contentEditable="true"
                onInput={ this.handleInput.bind(this) }
                className={ className }
                dangerouslySetInnerHTML={ this.createMarkup() }
            >
            </span>
        );
    }
}

ContentEditable.propTypes = {
    value: React.PropTypes.string.isRequired
};

export default ContentEditable;

Anyone come across this before?

Depute answered 10/11, 2016 at 22:0 Comment(7)
edit: happens in Safari & Firefox, not in Chrome.Shama
Hmmm, interesting. Just tried your example in Firefox and I get the issue. Caret moves to the start of the field when I type in there. I'm on OSX. Firefox 48.0.2 and Safari 10.0.1 [edit: ah, ok. thanks for creating the demo though! jsbin.com/gerifud/edit?js,output ]Depute
Check out some of the answers here: https://mcmap.net/q/127915/-react-js-onchange-event-for-contenteditable/6941627Shama
How did you solve this?Agonic
any solutions ? pls share , i have got similar issueHealy
Works with event.target.textContentQnp
for me @Ranjith Kumar answer worked: #55881897Salmonoid
G
4

You can achieve a contenteditable by adding a handler to an HTML tag. The problem is every time content is re-rendered the position of the cursor is reset. You are triggering a setState when the content of the span is edited, which is resetting the cursor position.

You could rather store a reference to the span tag and update its value rather than triggering a state change.

A ref could be stored which will inject the value in the span.

class ContentEditable extends Component {
  constructor(props) {
    super(props);
    this.spanRef = React.createRef();
    this.val = props.value;
  }

  handleInput(event) {
    const { innerText } = event.target;
    if (this.val !== innerText) {
      this.spanRef.innerText = innerText;
      this.val = innerText;
    }
  }

  render() {
    return (
      <span
        contentEditable="true"
        className={"is-valid"}
        onInput={this.handleInput.bind(this)}
        ref={this.spanRef}
        suppressContentEditableWarning={true}
      >
        {this.val}
      </span>
    );
  }
}

Running code

Gerfen answered 2/8, 2020 at 14:34 Comment(1)
not working position jumps to starting positionStep
I
0

I know I am late to the party, but this will might help someone else, or you can also find similar type of solution on other questions too.

Html works of Node concept when you update or print value in node directly because of react re-rending it reset the cursor position to start. you have to pass value programatically to element using the HTML web API's here is the modified code.

class ContentEditable extends Component {
  constructor(props) {
    super(props);
    this.spanRef = React.createRef();
    this.val = props.value;
  }

  componentDidUpdate(prevState, prevPorps){
    if(prevPorps?.value !== this.props.value) {
       this.spanRef.innerText = this.props.value;
    }
  }

  handleInput(event) {
    const { innerText } = event.target;
    if (this.val !== innerText) {
      this.spanRef.innerText = innerText;
      this.val = innerText;
    }
  }

  render() {
    return (
      <span
        contentEditable="true"
        className={"is-valid"}
        onInput={this.handleInput.bind(this)}
        ref={this.spanRef}
        suppressContentEditableWarning={true}
      >
      </span>
    );
  }
}
Impertinent answered 15/3 at 7:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.