Slbox
Slbox

Reputation: 13148

How can CKEditor be used with React.js in a way that allows React to recognize it?

I've tried using componentWillMount and componentDidMount to initialize CKEditor from within the context of React, but it doesn't seem to work no matter what combination I try. Has anyone found a solution to this besides switching editors?

Upvotes: 16

Views: 58027

Answers (5)

xiongkailing
xiongkailing

Reputation: 57

Just refer the ckeditor.js in index.html, and use it with window.CKEDITOR. Don't use CKEDITOR straight like the document in React component.

Just read the first-line of ckeditor.js, you will find what about define of CKEDITOR.

Upvotes: 2

codeslayer1
codeslayer1

Reputation: 3686

I published a package on Npm for using CKEditor with React. It takes just 1 line of code to integrate in your project.

Github link - https://github.com/codeslayer1/react-ckeditor.

How to Use?

  • Install the package using npm install react-ckeditor-component --save.
  • Then include the component in your React app and pass it your content and any other props that you need(all props listed on Github page) -

<CKEditor activeClass="editor" content={this.state.content} onChange={this.updateContent} />

The package uses the default build of CKEditor but you can use a custom build as well along with any of the plugins you like. It also includes a sample application. Hope you will find it useful.

Upvotes: 28

David Silva
David Silva

Reputation: 1091

Thanks to Sage, Sander & co. I just wanted to contribute a version for the "inline" mode of CKEditor.

First, disable CKEditor's "auto-inline" behavior with...

CKEDITOR.disableAutoInline = true

Then, for the actual component...

import React, {Component} from 'react';

export default class CKEditor extends Component {
    constructor(props) {
        super(props);
        this.elementName = "editor_" + this.props.id;
        this.componentDidMount = this.componentDidMount.bind(this);
        this.onInput = this.onInput.bind(this);
    }

    onInput(data) {
        console.log('onInput: ' + data);
    }

    render() {
        return (
            <div 
                contentEditable={true} 
                suppressContentEditableWarning
                className="rte"
                id={this.elementName}> 
                {this.props.value}</div>
        )
    }

    componentDidMount() {
        let configuration = {
            toolbar: "Basic"
        };
        CKEDITOR.inline(this.elementName, configuration);
        CKEDITOR.instances[this.elementName].on("change", function() {
            let data = CKEDITOR.instances[this.elementName].getData();
            this.onInput(data);
        }.bind(this));
    }
}

Usage would be something like this:

<CKEditor id="102" value="something" onInput={this.onInput} />

Upvotes: 0

Sander Verhagen
Sander Verhagen

Reputation: 9148

Sage describes an awesome solution in his answer. It was a lifesaver, as I've only just started using React, and I needed it to get this going. I did, however, change the implementation, also incorporating Jared's suggestions (using componentDidMount). Also, my need was to have a change callback, like so:

Usage of the component:

<CKEditor value={this.props.value} onChange={this.onChange}/>

Added this to index.html:

<script src="//cdn.ckeditor.com/4.6.1/basic/ckeditor.js"></script>

Using the following component code:

import React, {Component} from "react";

export default class CKEditor extends Component {
  constructor(props) {
    super(props);
    this.componentDidMount = this.componentDidMount.bind(this);
  }

  render() {
    return (
      <textarea name="editor" cols="100" rows="6" defaultValue={this.props.value}></textarea>
    )
  }

  componentDidMount() {
    let configuration = {
      toolbar: "Basic"
    };
    CKEDITOR.replace("editor", configuration);
    CKEDITOR.instances.editor.on('change', function () {
      let data = CKEDITOR.instances.editor.getData();
      this.props.onChange(data);
    }.bind(this));
  }
}

Again, all credits to Sage!


The following is an improved version of the basic version above, which supports multiple CKEditor instances on the same page:

import React, {Component} from "react";

export default class CKEditor extends Component {
  constructor(props) {
    super(props);
    this.elementName = "editor_" + this.props.id;
    this.componentDidMount = this.componentDidMount.bind(this);
  }

  render() {
    return (
      <textarea name={this.elementName} defaultValue={this.props.value}></textarea>
    )
  }

  componentDidMount() {
    let configuration = {
      toolbar: "Basic"
    };
    CKEDITOR.replace(this.elementName, configuration);
    CKEDITOR.instances[this.elementName].on("change", function () {
      let data = CKEDITOR.instances[this.elementName].getData();
      this.props.onChange(data);
    }.bind(this));
  }
}

Please note that this requires some unique ID to be passed along as well:

<CKEditor id={...} value={this.props.value} onChange={this.onChange}/>

Upvotes: 16

Sage
Sage

Reputation: 4937

This is for a React component which displays a P paragraph of text. If the user wants to edit the text in the paragraph, they can click it which will then attach a CKEditor instance. When the user is done altering the text in the Editor instance, the "blur" event fires which transfers the CKEditor data to a state property and destroys the CKEditor Instance.

import React, {PropTypes, Component} from 'react';

export default class ConditionalWYSIWYG extends Component {
    constructor(props) {
        super(props);
        this.state = {
            field_name:this.props.field_name,
            field_value:this.props.field_value,
            showWYSIWYG:false
        };
        this.beginEdit = this.beginEdit.bind(this);
        this.initEditor = this.initEditor.bind(this);
    }
    render() {
        if ( this.state.showWYSIWYG  ) {
            var field = this.state.field_name;
            this.initEditor(field);
            return (
                <textarea name='editor' cols="100" rows="6" defaultValue={unescape(this.state.field_value)}></textarea>
            )
        } else {
            return (
                <p className='description_field' onClick={this.beginEdit}>{unescape(this.state.field_value)}</p>
            )
        }
    }
    beginEdit() {
        this.setState({showWYSIWYG:true})
    }
    initEditor(field) {
        var self = this;

        function toggle() {
            CKEDITOR.replace("editor", { toolbar: "Basic", width: 870, height: 150 });
            CKEDITOR.instances.editor.on('blur', function() {

                let data = CKEDITOR.instances.editor.getData();
                self.setState({
                    field_value:escape(data),
                    showWYSIWYG:false
                });
                self.value = data;
                CKEDITOR.instances.editor.destroy();
            });
        }
        window.setTimeout(toggle, 100);
    }
}

The self.value = data allows me to retrieve the text from the parent component via a simple ref

The window.setTimeout(); gives React time to do what it does. Without this delay, I would get an Cannot read property 'getEditor' of undefined error in the console.

Hope this helps

Upvotes: 7

Related Questions