React

Github

This guide applies specifically to those applications using the component.

React is opinionated about when and where side effects can take place in a React component's life cycle, therefore some care is needed when invokingcodox session start and stop functions.

The following examples assume that when the user selects a document, the component handling the event will fetch the document's content from the backend, then pass the document content and the document identifier as props to the child component. The child component is called RealTimeFroalaEditor, which is a wrapper for the FroalaEditor React component.

A Simple case

We start by considering the simple case where the RealTimeFroalaEditor component is mounted and unmounted whenever the user switches to a different document.

Start

Just like the plain JavaScript session initialization code, add an initializedevent handler to Froala's config object. Our event handler delegates all the work to the froalaInitialized function by passing onthis , which in the context of the handler is a reference to the Froala editor itself. Inside froalaInitialized,we create a new codox object, maintain a reference to it as this.codox and finally invokes init. The config parameters docId, apiKey, username are assumed to be passed to the component as props. See this for details about the bootstrapping parameters.

The setTimeoutis used to wait for Froala to fully initialize its DOM structure. This is currently a work around and will likely be rectified on a subsequent react-froala-wysiwig release.

Stop

To leave the session when the user navigates away from the document (or chooses to open another document), simply add this.codox.stop()inside the React life cycle method componentWillUnmount.

//Example: React Froala component is mounted and unmounted when switching
//to a new document. 
import React from 'react';

import 'froala-editor/js/froala_editor.pkgd.min.js';
import 'froala-editor/css/froala_style.min.css';
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'font-awesome/css/font-awesome.css';

import FroalaEditor from 'react-froala-wysiwyg';

class RealTimeFroalaEditor extends React.Component {

  constructor () {
    super();
  }

  componentWillUnmount(){
    //leave the session
    if (this.codox) {
      this.codox.stop();
    }
  }

  froalaInitialized = (editor) =>  {
    //assume these are passed in from parent
    const {apiKey, docId, username} = this.props;
    
    //instantiate a Codox 
    this.codox = new Codox();
    
    setTimeout(() =>  {
      //start or join the session
      this.codox.init({
        app      : 'froala',
        username : username,
        docId    : docId,
        apiKey   : apiKey,
        editor   : editor
      });
    }, 100);
  }

  render () {
    const self = this;

    //add a froala initalized handler to bootstrap 
    //codox
    const config={
      events : {
        'initialized':  function() {
          self.froalaInitialized(this) 
        }
      }
    }
      
    return (
      <FroalaEditor
        config={config}
        model={this.props.model}
      />
    )
  }
}

export default RealTimeFroalaEditor;

Controlled Component

Another common scenario is where FroalaEditor is used as a fully controlled component, where the content state is managed in the parent component. When the user switches to a different document, this requires a new document content state to be passed into the editor component (as props), rather than recreating the editor.

Start

As in the previous example, we need to add an event handler to call codox.init when FroalaEditor is fully initialized. We also need to handle the situation when the editor receives a new document state, for example, when a user switches to a different document. We extend the RealTimeFroalaEditorcomponent by first wrapping the codoxsession bootstrapping code into a helper function called startCollaboration. Then we add the componentDidUpdate life cycle method where we call startCollaboration if the previous document identifier is different from the new document identifier.

import React from 'react';
import 'froala-editor/js/froala_editor.pkgd.min.js';
import 'froala-editor/css/froala_style.min.css';
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'font-awesome/css/font-awesome.css';
import FroalaEditor from 'react-froala-wysiwyg';

class RealTimeFroalaEditor extends React.Component {

  constructor () {
    super();
  }

  componentWillUnmount(){
    //leave the session
    if (this.props.codox) {
      this.props.codox.stop();
    }
  }
  
  componentDidUpdate(prevProps) {
    //if the editor needs to load content from a different doc
    if (this.props.docId!== prevProps.docId) {
      this.startCollaboration();
    }
  }

 startCollaboration = () => {
    const {codox, apiKey, docId, username} = this.props;

    setTimeout(() =>  {
      codox.init({
        app      : 'froala',
        username :  username,
        docId    :  docId,
        apiKey   : apiKey,
        editor   : this.editor
      });
    }, 100);
  }
  
  froalaInitialized = (editor) =>  {
    this.editor = editor;
    this.startCollaboration();
  }

  render () {
    var self = this;
    
    //add a froala initalized handler to bootstrap codox
    const config={
      events : {
        'initialized':  function() {
          self.froalaInitialized(this) 
        }
      }
    }
      
    return (
      <FroalaEditor
        config={config}
        model={this.props.model}
        onChange={this.props.onChange}
      />
    )
  }
}

export default RealTimeFroalaEditor;

Stop

Notice that we no longer create codox instances inside RealTimeFroalaEditor,instead we pass it into the component as a prop.The reason for this is that we must leave an existing session by invoking codox.stop before the FroalaEditoris updated with new content that corresponds to a different document.

Calling codox.stop incomponentDidUpdateis not safe because the new document content will have over-written the shared document state across all the users in the session.

To illustrate how this works, we add a skeleton implementation of a parent component for RealTimeFroalaEditor. We simulate a primitive content management interface that displays clickable links corresponding to the documents. When the user clicks on each link, we check if a new document state should be passed into the editor component by comparing the document identifiers. If new content should be loaded then we call codox.stop to leave the current session, before triggering the re-render through setState

import React from 'react';
import {Fragment} from 'react';
import RealTimeFroalaEditor from './RealTimeFroalaEditor';
const apiKey = 'd5cc1f48-356b-4032-8d0c-ba1a79396f79';
const username = 'Jessie';
const docs = [
{"id": "doc1", "content": "Hello World"},
{"id":"doc2", "content": "One two three"}
];

class App extends React.Component {
  
  constructor(){
    super();
    this.state = {
      id: null,
      content: null,
    };
  }

  onChanged = ({id, content}) => {
     //handle content change
  }

  onClick = ({id, content}) => {
   //When user switch to a document   
    if (id !== this.state.id){
      //leave the current session
      if (this.codox) this.codox.stop();
      
      //create a new codox instance
      this.codox = new Codox();
      
      //setState happens afterwards
      this.setState({
        id: id,
        content: content,
      });
    }
  }

  render() {
    return (
      <Fragment>
        <ul>
          {
            docs.map(doc => {
              return (
                <li onClick={() => this.onClick(doc)} key={doc.id}>
                  <a>{doc.id}</a>
                </li>
              );
            })
          }
        </ul>
       {this.state.id?     
        <RealTimeFroalaEditor 
          username={username}
          apiKey={apiKey}
          codox={this.codox} 
          docId={this.state.id}
          model={this.state.content}
        />: null}
      }
      </Fragment>
    )
  }
}

export default App;

That's it! With a little more care, integrating Codox with Froala React is fairly straight forward. Please let us know if you run into any challenges that we haven't covered here.

Last updated