reactjstypescriptdraftjsreact-draft-wysiwyg

Draft.js (react-draft-wysiwyg): text update from the outside of the Editor component does not work


I'm trying to update the text from a calling component into the Editor component. I use the props to pass the text from the caller, but when the text change (props is updated in the ContentEditor) the text in the Editor component is not:

Here is the code of the calling component:

<ControlledEditor htmlContent={this.state.validationResultContent}/>

Here is the code of the Controlled Editor:

    export class ControlledEditor extends Component {
    constructor(props) {
        super(props);
        this.state = {editorState: EditorState.createWithText(this.props.htmlContent)};
    }


    onEditorStateChange = (editorState) => {
        this.setState({ editorState })
    };

    render() {

        const { editorState } = this.state;

        return (
            <>
                <Container className="mt-5">
                    <Row>
                        <Editor
                            editorState= {editorState}
                            onEditorStateChange={this.onEditorStateChange}
                            wrapperClassName="demo-wrapper"
                            editorClassName="demo-editor"

                        />
                    </Row>
                </Container>
            </>
        );
    }
}

ControlledEditor.propTypes = {
    htmlContent: PropTypes.string
}

thanks for the help

------- UPDATE 1 -------

Following the working code (for me):

export class ControlledEditor extends Component {
    constructor(props) {
        super(props);
        this.state = {editorState: EditorState.createEmpty()}
    }

    componentDidUpdate(prevProps) {
        if (this.props.htmlContent !== prevProps.htmlContent) {
            this.setState({
                    editorState: EditorState.createWithContent(ContentState.createFromBlockArray(convertFromHTML(this.props.htmlContent)))
            });
        }
    }

    onEditorStateChange = (editorState) => {
        this.setState({editorState})
    };

    render() {
        const {editorState} = this.state;
        return (
            <>
                <Container className="mt-5">
                    <Row>
                        <Editor
                            editorState={editorState}
                            wrapperClassName="demo-wrapper"
                            editorClassName="demo-editor"
                            onEditorStateChange={this.onEditorStateChange}
                        />
                    </Row>
                </Container>
                <Container className="mt-5">
                    <Row>
                        <div dangerouslySetInnerHTML={{__html: this.props.htmlContent}}/>
                    </Row>
                </Container>
              
            </>
        );
    }
}

ControlledEditor.propTypes = {
    htmlContent: PropTypes.string
}

Solution

  • Optional: Updating Draft.js Version

    Some of the properties that you are using don't seem to be documented. Perhaps they are from a previous version of draft-js? But I'll write this answer based on the current documentation.

    The onEditorStateChange prop of Editor should be renamed to onChange.

    There is no createFromText on EditorState. This replacement involves two steps:

    1. ContentState can be created from text with the static method ContentState.createFromText.
    2. EditorState can be created from the ContentState with the static method EditorState.createWithContent

    So the initial state should be:

    this.state = {
      editorState: EditorState.createWithContent(
        ContentState.createFromText(this.props.htmlContent)
      )
    };
    

    The Core Problem

    With that out of the way, I am able to run your code and reproduce your issue:

    I use the props to pass the text from the caller, but when the text change (props is updated in the ContentEditor) the text in the Editor component is not.

    You are creating this.state.editorState based on the value of this.props.htmlContent in the constructor. That part of the code only runs one time when the ControlledEditor component first mounts. It does re-run when the props change, so it can't respond to changes in this.props.

    You would need to add a componentDidUpdate lifecycle method for that.

    componentDidUpdate(prevProps) {
      if (this.props.htmlContent !== prevProps.htmlContent) {
        this.setState({
          editorState: EditorState.createWithContent(
            ContentState.createFromText(this.props.htmlContent)
          )
        });
      }
    }
    

    In my opinion, it's easier to convert this to a function component and use hooks.

    import { useEffect, useState } from "react";
    import { Editor, EditorState, ContentState } from "draft-js";
    import "draft-js/dist/Draft.css";
    
    // helper function
    const createState = (text) => {
      return EditorState.createWithContent(ContentState.createFromText(text));
    };
    
    const ControlledEditor = ({ htmlContent }) => {
      // define the local state, using the createState callback to create the initial value
      const [editorState, setEditorState] = useState(createState(htmlContent));
    
      // override the local state any time that the props change
      useEffect(() => {
        setEditorState(createState(htmlContent));
      }, [htmlContent]);
    
      return (
        <Editor
          editorState={editorState}
          onChange={setEditorState}
        />
      );
    };
    
    export default function App() {
      const [text, setText] = useState("Hello World");
      return (
        <div>
          <h2>Source Text</h2>
          <textarea value={text} onChange={(e) => setText(e.target.value)} />
          <h2>Editor</h2>
          <ControlledEditor htmlContent={text} />
        </div>
      );
    }