React integration

TinyMCE React integration quick start guide

The Official TinyMCE React component integrates TinyMCE into React projects. This procedure creates a basic React application containing a TinyMCE editor based on our Basic example.

For examples of the TinyMCE integration, visit the tinymce-react storybook.

Prerequisites

This procedure requires:

Procedure

  1. Use the Create React App package to create a new React project named tinymce-react-demo.

     $ npx create-react-app tinymce-react-demo
  2. Change to the newly created directory.

     $ cd tinymce-react-demo
  3. Install the tinymce-react package and save it to your package.json with --save.

     $ npm install --save @tinymce/tinymce-react
  4. Using a text editor, open ./src/App.js and replace the contents with:

    import React, { useRef } from 'react';
    import { Editor } from '@tinymce/tinymce-react';
    
    export default function App() {
      const editorRef = useRef(null);
      const log = () => {
        if (editorRef.current) {
          console.log(editorRef.current.getContent());
        }
      };
      return (
        <>
          <Editor
            onInit={(evt, editor) => editorRef.current = editor}
            initialValue="<p>This is the initial content of the editor.</p>"
            init={{
              height: 500,
              menubar: false,
              plugins: [
                'advlist autolink lists link image charmap print preview anchor',
                'searchreplace visualblocks code fullscreen',
                'insertdatetime media table paste code help wordcount'
              ],
              toolbar: 'undo redo | formatselect | ' +
              'bold italic backcolor | alignleft aligncenter ' +
              'alignright alignjustify | bullist numlist outdent indent | ' +
              'removeformat | help',
              content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
            }}
          />
          <button onClick={log}>Log editor content</button>
        </>
      );
    }

    This JavaScript file will create the class App containing a TinyMCE editor configured to replicate the example on the Basic example page.

  5. Provide access to TinyMCE using either Tiny Cloud or by self-hosting TinyMCE.

    • Tiny Cloud

      Include the apiKey option in the editor element and include your Tiny Cloud API key.

      Such as:

      <Editor apiKey='your-api-key' init={{ /* your other settings */ }} />
    • TinyMCE Self-hosted

      TinyMCE can be self-hosted by: deploying TinyMCE independent of the React application, or bundling TinyMCE with the React application.

      • Deploy TinyMCE independent of the React application using tinymceScriptSrc

        To use an independent deployment of TinyMCE, add the tinymceScriptSrc prop to specify the path to the TinyMCE script, such as:

        <Editor
          tinymceScriptSrc="/path/to/tinymce.min.js">
        </Editor>

        To use tinymceScriptSrc with the create-react-app project, put the TinyMCE distribution in ./public folder and reference the path to the public folder using the environment variable process.env.PUBLIC_URL, such as:

        <Editor tinymceScriptSrc={process.env.PUBLIC_URL + '/tinymce/tinymce.min.js'}></Editor>
      • Deploy TinyMCE independent of the React application with a script tag

        An alternative method is to add a script to either the <head> or the end of the <body> of the HTML file, such as:

        <script src="/path/to/tinymce.min.js"></script>

        To use the independently sourced TinyMCE with create-react-app, add the script tag to ./public/index.html.

        Normally the tinymce distribution would be put in the public folder and referenced using the URL %PUBLIC_URL%/tinymce/tinymce.min.js, such as:

        <script src="%PUBLIC_URL%/tinymce/tinymce.min.js"></script>

        For information on self-hosting TinyMCE, see: Installing TinyMCE.

      • Bundling TinyMCE with the React application using a module loader

        Tiny does not recommend bundling tinymce and tinymce-react with a module loader. Bundling these packages can be complex and error prone.

        To bundle TinyMCE using a module loader (such as Webpack and Browserify), see: Usage with module loaders.

        Example of bundling:

        import { Editor } from '@tinymce/tinymce-react';
        
        // TinyMCE so the global var exists
        // eslint-disable-next-line no-unused-vars
        import tinymce from 'tinymce/tinymce';
        
        // Theme
        import 'tinymce/themes/silver';
        // Toolbar icons
        import 'tinymce/icons/default';
        // Editor styles
        import 'tinymce/skins/ui/oxide/skin.min.css';
        
        // importing the plugin js.
        import 'tinymce/plugins/advlist';
        import 'tinymce/plugins/autolink';
        import 'tinymce/plugins/link';
        import 'tinymce/plugins/image';
        import 'tinymce/plugins/lists';
        import 'tinymce/plugins/charmap';
        import 'tinymce/plugins/hr';
        import 'tinymce/plugins/anchor';
        import 'tinymce/plugins/searchreplace';
        import 'tinymce/plugins/wordcount';
        import 'tinymce/plugins/code';
        import 'tinymce/plugins/fullscreen';
        import 'tinymce/plugins/insertdatetime';
        import 'tinymce/plugins/media';
        import 'tinymce/plugins/nonbreaking';
        import 'tinymce/plugins/table';
        import 'tinymce/plugins/template';
        import 'tinymce/plugins/help';
        
        // Content styles, including inline UI like fake cursors
        /* eslint import/no-webpack-loader-syntax: off */
        import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css';
        import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css';
        
        export default function TinyEditorComponent(props) {
          // note that skin and content_css is disabled to avoid the normal
          // loading process and is instead loaded as a string via content_style
          return (
            <Editor
              init={{
                skin: false,
                content_css: false,
                content_style: [contentCss, contentUiCss].join('\n'),
              }}
            />
          );
        }
  6. Test the application using the Node.js development server.

    • To start the development server, navigate to the tinymce-react-demo directory and run:

      npm run start
    • To stop the development server, select on the command line or command prompt and press Ctrl+C.

Deploying the application to a HTTP server.

The application will require further configuration before it can be deployed to a production environment. For information on configuring the application for deployment, see: Create React App - Deployment.

To deploy the application to a local HTTP Server:

  1. Navigate to the tinymce-react-demo directory and run:

     $ npm run build
  2. Copy the contents of the tinymce-react-demo/build directory to the root directory of the web server.

The application has now been deployed on the web server.

Additional configuration is required to deploy the application outside the web server root directory, such as http://localhost:<port>/my_react_application.

Next Steps

TinyMCE React technical reference

Covered in this section:

Installing the TinyMCE React integration using NPM or Yarn

To install the tinymce-react package and save it to your package.json.

$ npm install --save @tinymce/tinymce-react

or with Yarn

$ yarn add @tinymce/tinymce-react

Using TinyMCE React integration in a Bootstrap dialog

To use the TinyMCE React integration inside Bootstrap UI dialogs, add the following React effect hook to a component that renders with the editor. This code is required because Bootstrap blocks all focusin calls from elements outside the dialog.

Bootstrap 5

For Bootstrap 5, the React effect hook contains no JQuery and does not support Microsoft Internet Explorer 11.
useEffect(() => {
  const handler = (e) => {
    if (e.target.closest(".tox-tinymce-aux, .moxman-window, .tam-assetmanager-root") !== null) {
      e.stopImmediatePropagation();
    }
  };
  document.addEventListener("focusin", handler);
  return () => document.removeEventListener("focusin", handler);
}, []);

Bootstrap 4

useEffect(function() {
  var handler = function(e) {
    if ($(e.target).closest(".tox-tinymce, .tox-tinymce-aux, .moxman-window, .tam-assetmanager-root").length) {
      e.stopImmediatePropagation();
    }
  };
  $(document).on('focusin', handler);
  return function() {
    $(document).off('focusin', handler);
  };
}, []);

Configuring the editor

The tinymce-react component provides props for:

Configuring editor source

The tinymce-react integration will try to source TinyMCE in the following order:

  1. The global tinymce will be used, if it is present on the page.

  2. If the tinymceScriptSrc prop is provided, then a script tag will be added to the page to load TinyMCE from the given URL.

  3. If none of the above conditions apply, then a script tag will be added to the page to load TinyMCE from Tiny Cloud.

These props are used to configure how the editor is sourced:

apiKey

The Tiny Cloud API key. When loading from Tiny Cloud, use this prop to remove the "This domain is not registered…​" warning message.

cloudChannel

The channel of TinyMCE used when loading from Tiny Cloud.

scriptLoading

The script loading behavior prop. Allows setting of the async and defer attributes, as well as adding an additional delay in milliseconds.

tinymceScriptSrc

The URL to use for sourcing TinyMCE, when loading a self-hosted version of TinyMCE.

Configuring page elements

These props provide some control over the page elements that the integration creates:

id

The id attribute of the element that the editor is initialized on.

inline

Load the editor as part of the page; sharing the page styles and selection.

tagName

The tag used for creating an inline editor. Ignored for a classic (iframe) editor.

textareaName

The name attribute on the textarea tag (HTML element). Used for creating the classic (iframe) editor. Ignored for an inline editor.

Configuring editor settings

These props are read when the editor is initialized. Changes after the editor has launched are ignored.

init

Additional options passed to TinyMCE when it is initialized.

plugins

Specify the editor plugins. This will be combined with plugins in the init prop.

toolbar

Specify the editor toolbar. This will override the toolbar in the init prop.

Managing the editor

These props can be updated after the editor is initialized. Note that there are other events not mentioned here.

disabled

Should the editor be in read-only mode.

initialValue

The starting value of the editor. Changing this value after the editor has loaded will reset the editor (including the editor content).

onBeforeAddUndo

An event handler for notifying when the editor is about to create an undo level, and preventing it if required. This is important for controlled components that restrict the allowed values of the editor.

onEditorChange

An event handler for detecting editor changes. Useful when implementing TinyMCE as a controlled component.

onInit

An event handler for notifying when the editor has initialized. Useful for getting the initial value of the editor or obtaining a reference to the editor that can be used for a uncontrolled component.

value

Sets and enforces the value of the editor. Only used for a controlled component.

Available props

None of the configuration props are required for the TinyMCE React component; however, if the apiKey prop is not configured when loading from Tiny Cloud, a warning message will be shown in the editor. For guidance about which props to use, see: Configuring the editor.

apiKey

Tiny Cloud API key.

Required for deployments using the Tiny Cloud to provide the TinyMCE editor without the warning message "This domain is not registered…​".

To register for a Tiny Cloud API key, visit the Tiny Account sign-up page. To retrieve the Tiny Cloud API key for an existing Tiny Account, login and visit the Tiny Account Dashboard.

Default value: no-api-key

Type: String

Example: Using apiKey
<Editor apiKey="your-api-key"></Editor>

cloudChannel

Changes the TinyMCE build used for the editor to either a specific version or a channel indicating a stability level.

Default value: 5-stable

Possible values: 5-stable, 5-testing, 5-dev, 5.10

Changes the TinyMCE build used for the editor to one of the following Tiny Cloud channels:

  • 5-stable (Default): The current enterprise release of TinyMCE.

  • 5-testing: The current release candidate for the next enterprise release of TinyMCE.

  • 5-dev: The nightly-build version of TinyMCE.

  • A version number such as 5.10: The specific enterprise release version of TinyMCE.

Such as:

<Editor
  apiKey='your-api-key'
  cloudChannel='5-dev'
  init={{ /* your other settings */ }}
/>

disabled

The disabled prop can dynamically switch the editor between a "disabled" (read-only) mode (true) and the standard editable mode (false).

Default value: false

Possible values: true, false

Example: Using disabled
<Editor
  disabled={true}
/>

id

An id for the editor. Used for retrieving the editor instance using the tinymce.get('ID') method.

Default value: Automatically generated UUID.

Type: String

Example: Using id
<Editor id="your-id"></Editor>

init

Additional settings passed to the tinymce.init({...}) method used to initialize the editor.

For information on the TinyMCE tinymce.init({...}) method, see: Basic setup.

When using tinymce-react: - The init prop does not require the selector or target options - If the selector, target, or readonly options are set using the init prop, they will be overridden by the integration.

Default value: { }

Type: Object

Example: Using init
<Editor
  init={{
    plugins: [
      'lists link image paste help wordcount'
    ],
    toolbar: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | help'
  }}
/>

initialValue

The initial HTML content of the editor. This will reset the editor undo state and the cursor position when changed.

This may be set either before the editor loads, or soon afterwards by an asynchronous process.

Ensure that this is not updated by onEditorChange or the editor will be unusable.

Default value: ''

Type: String

Example: Using static initialValue
<Editor initialValue="<p>Once upon a time...</p>"></Editor>
Example: Using asynchronous initialValue
const [initialValue, setInitialValue] = useState(undefined);
useEffect(() => {
  // a real application might do a fetch request here to get the content
  setTimeout(() => setInitialValue('<p>Once upon a time...</p>'), 500);
}, []);

return (
  <Editor
    initialValue={initialValue}
  />
);

inline

Used to set the editor to inline mode. Using <Editor inline={true} /> is the same as setting {inline: true} in the TinyMCE tinymce.init({...}) method.

For information on inline mode, see: User interface options - inline and Setup inline editing mode.

Default value: false

Possible values: true, false

Example: Using inline
<Editor
  inline={true}
/>

onEditorChange

Used to store the state of the editor outside the TinyMCE React component. This prop is commonly used when using the TinyMCE React component as a controlled component.

It is called with two arguments:

value

The current value of the editor. This is normally HTML but can be text if the deprecated outputFormat prop is used.

editor

A reference to the editor.

For detailed information on using onEditorChange, see: Using the TinyMCE React component as a controlled component.

Type: EventHandler

outputFormat

This option was deprecated with the release of the TinyMCE React component 3.11.0. The outputFormat option will be removed in a future release of the TinyMCE React component.

Used to specify the format of the content produced by the onEditorChange event.

This does not change the input format, so the editor must still be supplied HTML in the value or initialValue, which makes this prop much harder to use correctly than it initially seems.

Type: String

Default value: html

Possible values: html, text

Example: Using outputFormat
const textToHtml = (text) => {
  const elem = document.createElement('div');
  return text.split(/\n\n+/).map((paragraph) => {
    return '<p>' + paragraph.split(/\n+/).map((line) => {
      elem.textContent = line;
      return elem.innerHTML;
    }).join('<br/>') + '</p>';
  }).join('');
};
const initialText = 'The quick brown fox jumps over the lazy dog';
const [text, setText] = useState(initialText);
return (
  <>
    <Editor
      initialValue={textToHtml(initialText)}
      outputFormat='text'
      onEditorChange={(newText) => setText(newText)}
    />
    <pre>{text}</pre>
  </>
);
Example: Replacing usage of outputFormat
const [value, setValue] = useState('<p>The quick brown fox jumps over the lazy dog</p>');
const [text, setText] = useState('');

return (
  <>
    <Editor
      value={value}
      onInit={(evt, editor) => {
        setText(editor.getContent({format: 'text'}));
      }}
      onEditorChange={(newValue, editor) => {
        setValue(newValue);
        setText(editor.getContent({format: 'text'}));
      }}
    />
    <pre>{text}</pre>
  </>
);

plugins

Used to include plugins for the editor. Using <Editor plugins='lists' /> is the same as setting {plugins: 'lists'} in the TinyMCE tinymce.init({...}) method.

For information on adding plugins to TinyMCE, see: Add plugins to TinyMCE.

Type: String or Array

Example: Using plugins
<Editor plugins="lists code"></Editor>

scriptLoading

Used to configure the script tag created to load TinyMCE.

Contains 3 settings:

async

Sets the async attribute on the script tag created to load TinyMCE.

For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available.

Default value: false

defer

Sets the defer attribute on the script tag created to load TinyMCE.

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.

Default value: false

delay

The script tag to load TinyMCE will be added after the specified delay in milliseconds.

Default value: 0

Type: Object

  {
    async?: boolean;
    defer?: boolean;
    delay?: number;
  }
Example: Loading TinyMCE asynchronously
<Editor scriptLoading={{ async: true }}></Editor>
Example: Delaying load of TinyMCE for 500 milliseconds
<Editor scriptLoading={{ delay: 500 }}></Editor>

tagName

Only valid when <Editor inline={true} />. Used to define the HTML element for the editor in inline mode.

Default value: div

Type: String

Example: Using tagName
<Editor
  inline={true}
  tagName='section'
/>

textareaName

Only valid when the editor is in classic (iframe) mode. Sets the name attribute for the textarea element used for the editor in forms.

Default value: undefined

Type: String

Example: Using textareaName
<form method="post">
  <Editor textareaName="description"></Editor>
  <button type="submit">Submit</button>
</form>

toolbar

Used to set the toolbar for the editor. Using <Editor toolbar='bold' /> is the same as setting {toolbar: 'bold'} in the TinyMCE method tinymce.init({...}).

For information setting the toolbar for TinyMCE, see: User interface options - toolbar.

Type: String

Example: Using toolbar
<Editor plugins="code" toolbar="bold italic underline code"></Editor>

tinymceScriptSrc

Use the tinymceScriptSrc prop to specify an external version of TinyMCE to lazy load.

Type: String

Example: Using tinymceScriptSrc
<Editor tinymceScriptSrc="/path/to/tinymce.min.js"></Editor>

value

Sets the HTML content of the editor when operating as a controlled component.

When this prop is different to the current editor content, the editor content will be changed to match (within 200 milliseconds) and an undo level will be created. When the editor content changes by this mechanism, the editor will attempt to retain the selection, however if the previous selection does not exist in the new content, the cursor returns to the start of the document.

This prop allows the editor to be used as a controlled component by setting the value prop and using the onEditorChange event to update the value.

For detailed information on using the value prop, see: Using the TinyMCE React component as a controlled component.

Type: String

Using the TinyMCE React component as a uncontrolled component

The TinyMCE React component is designed to be used as an uncontrolled component, which allows the editor to perform well on larger documents.

When using the editor as an uncontrolled component, avoid using the value and onEditorChange props. Tiny recommends retrieving the editor content when it is needed. The onInit event handler can be used to store a editor reference when the editor is loaded to assist with retrieving the content.

To provide visual feedback to the user when the content is ready to be saved, use the onDirty event handler; combined with clearing the editor’s "dirty" state when saving the editor content.

The editor is "dirty" (or in a "dirty" state) when the user modifies editor content after initialization or the last tinymce.editor.save() call. This includes changes made using undo or redo.
Example: Functional uncontrolled component with save button and dirty state
function MyComponent({initialValue}) {
  const editorRef = useRef(null);
  const [dirty, setDirty] = useState(false);
  useEffect(() => setDirty(false), [initialValue]);
  const save = () => {
    if (editorRef.current) {
      const content = editorRef.current.getContent();
      setDirty(false);
      editorRef.current.setDirty(false);
      // an application would save the editor content to the server here
      console.log(content);
    }
  };
  return (
    <>
      <Editor
        initialValue={initialValue}
        onInit={(evt, editor) => editorRef.current = editor}
        onDirty={() => setDirty(true)}
      />
      <button onClick={save} disabled={!dirty}>Save</button>
      {dirty && <p>You have unsaved content!</p>}
    </>
  );
}

Using the TinyMCE React component as a controlled component

The controlled component can have performance problems on large documents as it requires converting the entire document to a string on each keystroke or modification.

To use the editor as a controlled component, both the value and onEditorChange props are required.

The value prop is used to set and re-set the editor content. If it is not updated to the latest version of the editor content, the editor will rollback any changes.

The onEditorChange prop is used to provide an event handler that will be run when any change is made to the editor content. Changes to the editor must be applied to the value prop within 200 milliseconds to prevent the changes being rolled back.

Example: Functional controlled component
function MyComponent({initialValue}) {
  const [value, setValue] = useState(initialValue ?? '');
  useEffect(() => setValue(initialValue ?? ''), [initialValue]);
  return (
    <Editor
      initialValue={initialValue}
      value={value}
      onEditorChange={(newValue, editor) => setValue(newValue)}
    />
  );
}
Example: Class controlled component
class MyComponent extends React.Component {
  constructor(props) {
    super(props);

 this.state = { value: props.initialValue ?? '' };
 this.handleEditorChange = this.handleEditorChange.bind(this);   }

componentDidUpdate(prevProps) {
    if (this.props.initialValue !== prevProps.initialValue) {
      this.setState({ value: this.props.initialValue ?? '' })
    }
  }

handleEditorChange(value, editor) {
    this.setState({ value });
  }

render() {
    return (
      <Editor
        initialValue={this.props.initialValue}
        value={this.state.value}
        onEditorChange={this.handleEditorChange}
      />
    )
  }
}

When the editor must be restricted to avoid invalid states, such as exceeding a maximum length, then a handler for onBeforeAddUndo must be added to avoid those states in the undo history.

Example: Limited length controlled component
function MyComponent({initialValue, limit}) {
  const sizeLimit = limit ?? 50;
  const [ value, setValue ] = React.useState(initialValue ?? '');
  const [ length, setLength ] = React.useState(0);

const handleInit = (evt, editor) => {
    setLength(editor.getContent({ format: 'text' }).length);
  };

const handleUpdate = (value, editor) => {
    const length = editor.getContent({ format: 'text' }).length;
    if (length \<= sizeLimit) {
      setValue(value);
      setLength(length);
    }
  };

const handleBeforeAddUndo = (evt, editor) => {
    const length = editor.getContent({ format: 'text' }).length;
    // note that this is the opposite test as in handleUpdate
    // because we are determining when to deny adding an undo level
    if (length > sizeLimit) {
      evt.preventDefault();
    }
  };

return (
    <>
      <Editor
        initialValue={initialValue}
        value={value}
        onInit={handleInit}
        onEditorChange={handleUpdate}
        onBeforeAddUndo={handleBeforeAddUndo}
      />
      <p>Remaining: {sizeLimit - length}</p>
    </>
  );
};

For information on controlled components in React, see: React Docs - Controlled Components.

Event binding

Functions can be bound to editor events, such as:

<Editor onSelectionChange={this.handlerFunction} />

When the handler is called (handlerFunction in this example), it is called with two arguments:

event

The TinyMCE event object.

editor

A reference to the editor.

The following events are available:

  • onActivate

  • onAddUndo

  • onBeforeAddUndo

  • onBeforeExecCommand

  • onBeforeGetContent

  • onBeforeRenderUI

  • onBeforeSetContent

  • onBeforePaste

  • onBlur

  • onChange

  • onClearUndos

  • onClick

  • onContextMenu

  • onCopy

  • onCut

  • onDblclick

  • onDeactivate

  • onDirty

  • onDrag

  • onDragDrop

  • onDragEnd

  • onDragGesture

  • onDragOver

  • onDrop

  • onExecCommand

  • onFocus

  • onFocusIn

  • onFocusOut

  • onGetContent

  • onHide

  • onInit

  • onKeyDown

  • onKeyPress

  • onKeyUp

  • onLoadContent

  • onMouseDown

  • onMouseEnter

  • onMouseLeave

  • onMouseMove

  • onMouseOut

  • onMouseOver

  • onMouseUp

  • onNodeChange

  • onObjectResizeStart

  • onObjectResized

  • onObjectSelected

  • onPaste

  • onPostProcess

  • onPostRender

  • onPreProcess

  • onProgressState

  • onRedo

  • onRemove

  • onReset

  • onSaveContent

  • onSelectionChange

  • onSetAttrib

  • onSetContent

  • onShow

  • onSubmit

  • onUndo

  • onVisualAid