Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
like image 244
user1283776 Avatar asked Mar 30 '18 22:03

user1283776


2 Answers

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>);
}
like image 199
Munim Munna Avatar answered Oct 11 '22 22:10

Munim Munna


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)

like image 32
Jose A. Ayllón Avatar answered Oct 11 '22 23:10

Jose A. Ayllón