Rendering ContentEditable component without getting ContentEditableWarning?


I get the following warning when rendering my component:

Warning: A component is contentEditable and contains children managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.

This is my component:

import React, { Component } from "react";

export default class Editable extends Component {
  render() {
    return (
      <div contentEditable={true} onBlur={this.props.handleBlur}>
        {this.props.children}
      </div>
    );
  }
}

This component from https://github.com/lovasoa/react-contenteditable does not generate the warning.

import React from 'react';

let stripNbsp = str => str.replace(/&nbsp;|\u202F|\u00A0/g, ' ');

export default class ContentEditable extends React.Component {
  constructor() {
    super();
    this.emitChange = this.emitChange.bind(this);
  }

  render() {
    var { tagName, html, ...props } = this.props;

    return React.createElement(
      tagName || 'div',
      {
        ...props,
        ref: (e) => this.htmlEl = e,
        onInput: this.emitChange,
        onBlur: this.props.onBlur || this.emitChange,
        contentEditable: !this.props.disabled,
        dangerouslySetInnerHTML: {__html: html}
      },
      this.props.children);
  }

  shouldComponentUpdate(nextProps) {
    let { props, htmlEl } = this;

    // We need not rerender if the change of props simply reflects the user's edits.
    // Rerendering in this case would make the cursor/caret jump

    // Rerender if there is no element yet... (somehow?)
    if (!htmlEl) {
      return true;
    }

    // ...or if html really changed... (programmatically, not by user edit)
    if (
      stripNbsp(nextProps.html) !== stripNbsp(htmlEl.innerHTML) &&
      nextProps.html !== props.html
    ) {
      return true;
    }

    let optional = ['style', 'className', 'disabled', 'tagName'];

    // Handle additional properties
    return optional.some(name => props[name] !== nextProps[name]);
  }

  componentDidUpdate() {
    if ( this.htmlEl && this.props.html !== this.htmlEl.innerHTML ) {
      // Perhaps React (whose VDOM gets outdated because we often prevent
      // rerendering) did not update the DOM. So we update it manually now.
      this.htmlEl.innerHTML = this.props.html;
    }
  }

  emitChange(evt) {
    if (!this.htmlEl) return;
    var html = this.htmlEl.innerHTML;
    if (this.props.onChange && html !== this.lastHtml) {
      // Clone event with Object.assign to avoid 
      // "Cannot assign to read only property 'target' of object"
      var evt = Object.assign({}, evt, { 
        target: { 
          value: html 
        } 
      });
      this.props.onChange(evt);
    }
    this.lastHtml = html;
  }
}

Questions:

  • What is the potential problem with my code that React wants to warn me about? I did not quite understand from reading the documentation at https://reactjs.org/docs/dom-elements.html.
  • Why doesn't the component from https://github.com/lovasoa/react-contenteditable generate the same warning?

The component you stated is generating component calling React.createElement as follows and thus avoiding the warning:

function ContentEditable(props) {
  return React.createElement('div', {contentEditable: true});
}

Whereas you are using jsx.

function ContentEditable(props) {
  return (<div contentEditable={true}></div>);
}

You can read more here about why react warns you. If you want to suppress the warning you can also use suppressContentEditableWarning attribute.

function ContentEditable(props) {
  return (<div contentEditable={true} suppressContentEditableWarning={true}></div>);
}

When you set contenteditable you're letting the content be modified in the browser.

If you change the DOM without React managing behind, React will warn you as this might cause issues when, for example, updating those elements. You are passing {this.props.children} which comes directly from React, using contenteditable will let your content be modified by a user in the browser, thus will might not be the same as React has no control anymore.

You can use suppressContentEditableWarning={true} to avoid that warning. You either can use React.createElement and pass an argument {contentEditable: true} to avoid the warning (As like https://github.com/lovasoa/react-contenteditable is doing)


I dont use external components.I had the same problem, Additionally I used to get the error that I cannot use dangerouslySetInnerHTML if i mark the div as content editable.My approach was similar to yours. So I didnt mark it as content-editable. Just dangerouslySetInnerHTML. but In componentDidUpdate I used the ref to setAttribute of the div as "contenteditable=true". I used the same div to grab the div content to save. This stopped all warnings and errors.

componentDidUpdate(){
    this.htmlEl.setAttribute("contenteditable", true);
}

Additionally if your dangerouslySetInnerHTML content contains react components, you can hydrate them to make them functional. but obviously you need to know which component is inside the div to hydrate it.