Blueprint by Tiny
Return to Tiny.cloud
Return to Tiny.cloudTry TinyMCE for Free
Search by

Emulating an online document editor with TinyMCE in React

Simon Fjeldså

October 20th, 2019

Written by

Simon Fjeldså
Simon Fjeldså

Category

Tips & How-Tos

When using TinyMCE in your React project, you want a fast and reliable way to integrate it with your existing codebase.

The Official TinyMCE React Component (tinymce-react) brings TinyMCE into the React world.

This blog post demonstrates how you can use tinymce-react to develop a simple document editor application.

Thanks to the tinymce-react component doing the heavy lifting (integrating TinyMCE with React), using it is often just a matter of dropping it into your project and providing the configuration you wish to have! 

If you’re looking for a basic introduction on how to get started with TinyMCE in React, check out the previous post on How to add TinyMCE to a simple React project.

Prerequisites

Since this post focuses on using the tinymce-react component, some prior knowledge of React is assumed. To view the full source code and related boilerplate code, open up the embedded CodeSandbox example above.

Getting started

To get started, we’ll need to install the tinymce-react component with our package manager of choice.

# If you use npm
$ npm install @tinymce/tinymce-react
# If you use Yarn
$ yarn add @tinymce/tinymce-react

The tinymce-react component is a wrapper around TinyMCE and thus requires TinyMCE in order to work. By default, the component will load TinyMCE from Tiny Cloud, which is the simplest and quickest way to get going. The only thing we need for this is an API key which anyone can get for free at tiny.cloud (including a 30 day trial of the premium plugins)! The alternative is to self-host TinyMCE and make it available, together with the assets it requires.

Importing and configuring tinymce-react

Using the component in a project couldn’t be easier – just import it and add the component with your desired configuration. Here, tinymce-react is imported as an ES module, but it is available as a CommonJS module as well.

We’re going to call our project class DocumentEditor and start by adding tinymce-react to its render method. Throughout this project, we’ll use a minimal amount of CSS to style our application. For brevity, this has been left out, but take a look at the CodeSandbox example to see how this has been done.

import { Editor } from "@tinymce/tinymce-react";
import * as React from "react";

class DocumentEditor extends React.Component {
  /* ... */

  render() {
    return (
      <Editor
        apiKey="Get your free API key at tiny.cloud and paste it here"
        init={{
          icons: "jam",
          skin: "fabric",
          content_css: "document",
          resize: false
        }}
      />
    );
  }
}

DocumentEditor.jsx

The tinymce-react component accepts a prop for the API key when using Tiny Cloud. We just have to add the API key that we got at tiny.cloud, and we’ll instantly have the configuration required to have a fully-fledged editor working!

Initial setup of TinyMCE in React with customized skin and icons.

The Tiny Skins and Icon Packs premium plugin makes it possible to customize the appearance of TinyMCE. In this project, we’re using Jam icons and the Fabric skin, but there are many more ready-made ones to choose from.

If you’ve just signed up for your Tiny Cloud API key, you have access to all the icons and skins (and all our premium plugins) for 30 days. So you are free to try them all out!

Adding a status bar and event handlers

A status bar is useful for all kinds of information and interactivity. There are many possibilities here – for example, you could have drop-down menus for loading stored documents or templates. In this example, we’re going to display the current document name, as well as a simple status with a custom StatusBar component.

As is good practice for state management, we’re going to keep all our state centralized in the DocumentEditor. Then, with the status bar being a simple functional component, we’ll have to pass down any state it requires, plus any event handlers for updating the current state.

import { Editor } from "@tinymce/tinymce-react";
import * as React from "react";
import { StatusBar } from "./StatusBar";

class DocumentEditor extends React.Component {
  /* ... */

  render() {
    return (
      <div className="document-editor">
                
        <StatusBar
          displayIsSaving={this.state.displayIsSaving}
          documentName={this.state.documentName}
          onNameChange={this.handleNameChange}
        />
                
        <Editor
          apiKey="Get your free API key at tiny.cloud and paste it here"
          onEditorChange={this.handleEditorChange}
          value={this.state.editorContent}
          init={{
            icons: "jam",
            skin: "fabric",
            content_css: "document",
            resize: false
          }}
        />
              
      </div>
    );
  }
  /* ... */

  handleEditorChange(editorContent) {
    this.save({ editorContent });
  }

  handleNameChange(documentName) {
    this.save({ documentName });
  }
}

DocumentEditor.jsx

The onEditorChange prop for the editor component accepts an event handler that will be invoked with the new editor content whenever an action is performed that changes it. The value prop used to set the editor’s content, together with onEditorChange, is used here to turn tinymce-react into a Controlled Component. What we’re doing is lifting all the state up and making the DocumentEditor’s state the single source of truth.

Implementing the status bar component

The StatusBar component is a simple functional component, since we’re storing the state we have at a higher level. We have a simple input field for displaying and modifying the document name, as well as a span container holding the current status. Some styling via classes is added as well in order to give our project a unified look.

import * as React from "react";

const StatusBar = props => {
  const handleBlur = e => {
    if (e.target.value !== props.documentName) {
      props.onNameChange(e.target.value);
    }
  };

  return (
    <header className="status-bar">
            
      <input
        className="font"
        defaultValue={props.documentName}
        onBlur={handleBlur}
      />
            
      <span className="font">
                 - {props.displayIsSaving ? "Saving" : "Saved"}
              
      </span>
          
    </header>
  );
};

StatusBar.jsx

Binding methods, initializing state, and auto-save functionality

In order to make the ‘this’ keyword in our event handlers refer to the context of the DocumentEditor class, we need to bind them in the constructor. We’ll also set the initial state, although, in a real-world scenario, this would be populated with a document loaded from a server.

To illustrate how to develop a document editor, we’re adding some fake auto-save functionality as well. This is purely to illustrate one of the many possibilities, and add some extra interactivity to our project. We invite everyone to explore their own ideas!

import { Editor } from "@tinymce/tinymce-react";
import * as React from "react";
import * as _ from "lodash";
import { StatusBar } from "./StatusBar";

class DocumentEditor extends React.Component {
  constructor(props) {
    super(props); // Binding to make 'this' work in the callbacks

    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleEditorChange = this.handleEditorChange.bind(this); // Initial state

    this.state = {
      documentName: "Document 1",
      editorContent: '<h2 style="text-align: center;">TinyMCE and React!</h2>',
      displayIsSaving: false
    }; // Fake async save method to illustrate auto-save functionality

    this.throttledSaveToServer = _.throttle(() => {
      setTimeout(() => {
        console.log(
          "Saved to server",
          this.state.documentName,
          this.state.editorContent
        );
        this.debouncedEndSaving();
      }, 500);
    }, 500);

    this.debouncedEndSaving = _.debounce(() => {
      this.setState({ displayIsSaving: false });
    }, 1000);
  }

  componentWillUnmount() {
    this.debouncedEndSaving.cancel();
    this.throttledSaveToServer.cancel();
  }
  /* ... */
}

DocumentEditor.jsx

To time and limit the amount of requests our example will make, we’re using two functions from a popular library called lodash. This is optional functionality, but if you’re curious, read more about the throttle and debounce functions.

We’ve also added the componentWillUnmount lifecycle hook. This will cancel any delayed invocations by our save methods if there are any dormant ones when the component gets unmounted.

Implementing the save method

Lastly, we’re going to implement the save method – a method that takes the state it should update in the form of an object. Whenever the state is being updated, we set a flag to indicate that saving is in progress in our status bar.

Since React might batch multiple setState calls into a single update for performance, any actions we want to perform with the state may be asynchronous. This is why our actual save action will be performed by the callback given to the setState method.

import { Editor } from "@tinymce/tinymce-react";
import * as React from "react";
import * as _ from "lodash";
import { StatusBar } from "./StatusBar";

class DocumentEditor extends React.Component {
  /* ... */

  save(newPartialState) {
    this.setState(
      {
        ...newPartialState,
        displayIsSaving: true
      },
      () => {
        this.throttledSaveToServer();
      }
    );
  }
  /* ... */
}

DocumentEditor.jsx

An alternative approach would be to use the componentDidUpdate lifecycle hook and do our network requests from there, but in our simple example, this should be sufficient.

The completed project with status bar displayed.

Wrapping up

That’s it!

We now have a document editor with powerful editing capabilities provided by TinyMCE, together with a modern look provided by the Tiny Skins and Icon Packs plugin.

From here, the possibilities are endless.

What are you doing with TinyMCE in React? Follow us on Twitter and let us know what you’re up to. Happy coding!

ReactIntegration
Simon Fjeldså
bySimon Fjeldså

Simon is an Engineer at Tiny, working on an array of features such as plugins and framework integrations for TinyMCE. Powered by coffee.

Related Articles

  • Depiction of three ways to try TinyMCE
    Tips & How-Tos

    3 ways to try TinyMCE (with live examples)

    by Ben Long in Tips & How-Tos

Build beautiful content for the web with Tiny.

The rich text editing platform that helped launch Atlassian, Medium, Evernote and more.

Begin my FREE 30 day trial
Tiny Editor