Integrating TinyMCE rich text editor with React

Do you need a WYSIWYG editor for your React application? Well, you're in the correct place. In this guide, I'll show you how to self-host and integrate TinyMCE to allow rich text editing in your app. Let's get started!

1. Set up React app

We'll begin by creating a React app, but if you've already done so, jump to the next step.

For creating our React app, we'll use Vite. Open a terminal window, point it to your desired work directory and run:

npm create vite@latest

If you haven't used Vite before, you'll get prompted to install the package before continuing.

In the project setup, I used the following options:

> npx > create-vite ✔ Project name: … react-tinymce-text-editor ✔ Select a framework: › React ✔ Select a variant: › TypeScript

I'm biased towards using TypeScript whenever possible, but feel free to use the option that you're most comfortable with and accommodate your needs. Mind that if you pick JavaScript, you'll need to tweak the file extensions and remove type definitions from the code provided in this guide.

Now, point to the newly created react-tinymce-text-editor project directory, and proceed to install the required packages:

cd react-tinymce-text-editor npm install

2. Add TinyMCE editor component

Install the required packages for integrating TinyMCE:

npm install tinymce @tinymce/tinymce-react   

Once that's done, let's create a new components folder in the src directory. In src/components create TextEditor.tsx where we'll add the code for the text editor component:

src/components/TextEditor.tsx

import { FunctionComponent, useRef, useState } from "react"; import "tinymce/tinymce"; import "tinymce/icons/default"; import "tinymce/models/dom/model"; import "tinymce/plugins/advlist"; import "tinymce/plugins/anchor"; import "tinymce/plugins/autolink"; import "tinymce/plugins/image"; import "tinymce/plugins/link"; import "tinymce/plugins/lists"; import "tinymce/plugins/searchreplace"; import "tinymce/plugins/table"; import "tinymce/plugins/wordcount"; // Here are some other plugins that might come in handy but are not used in this demo. // If you need one of these, make sure to add it to the list of plugins defined in the Editor component. // import "tinymce/plugins/autoresize"; // import "tinymce/plugins/autosave"; // import "tinymce/plugins/charmap"; // import "tinymce/plugins/code"; // import "tinymce/plugins/codesample"; // import "tinymce/plugins/directionality"; // import "tinymce/plugins/emoticons"; // import "tinymce/plugins/fullscreen"; // import "tinymce/plugins/importcss"; // import "tinymce/plugins/insertdatetime"; // import "tinymce/plugins/media"; // import "tinymce/plugins/nonbreaking"; // import "tinymce/plugins/pagebreak"; // import "tinymce/plugins/preview"; // import "tinymce/plugins/quickbars"; // import "tinymce/plugins/save"; // import "tinymce/plugins/visualblocks"; // import "tinymce/plugins/visualchars"; // Editor styles and theme. import "tinymce/skins/content/default/content.min.css"; import "tinymce/skins/ui/oxide/content.min.css"; import "tinymce/skins/ui/oxide/skin.min.css"; import "tinymce/themes/silver"; import { Editor } from "@tinymce/tinymce-react"; import { Editor as TinyMCEEditor } from "tinymce"; interface TextEditorProps { initialValue?: string; disabled?: boolean; } const TextEditor: FunctionComponent<TextEditorProps> = ({ initialValue, disabled, }) => { // Used for retrieving content from uncontrolled editor. const editorRef: React.MutableRefObject<TinyMCEEditor | null> = useRef(null); const [editorId, setEditorId] = useState<string>(); const [content, setContent] = useState<string>(); // Remove this if you need a controlled component. In that case, the onInit prop and editorRef can be removed as well. function handleContentSubmit() { if (editorRef.current) { // Get editor content from ref. const editorContent = editorRef.current.getContent(); console.log("Content saved: ", editorContent); setContent(editorContent); } } // If you need the editor component to be controlled, uncomment this and use the onEditorChange prop. Make sure to debounce it to avoid unnecessary re-rendering. // function onEditorContentChange(content: string, editor: TinyMCEEditor) { // console.log("Content changed: ", content); // setContent(content); // } return ( <> <label htmlFor={editorId} style={{ fontSize: "16px", fontWeight: "bold", }} > Content </label> <Editor onInit={(evt, editor) => (editorRef.current = editor)} initialValue={initialValue} // onEditorChange={onEditorContentChange} disabled={disabled} init={{ skin: false, // Use imported skin css. content_css: false, // Use imported content css. width: 875, height: 500, menubar: false, branding: false, license_key: "gpl", // Read about license keys at: https://www.tiny.cloud/docs/tinymce/latest/license-key/ plugins: [ "advlist", "anchor", "autolink", "image", "link", "lists", "searchreplace", "table", "wordcount", ], toolbar: "undo redo | blocks | " + "bold italic forecolor | alignleft aligncenter " + "alignright alignjustify | bullist numlist outdent indent | " + "removeformat | help", help_tabs: [ "shortcuts", // The default shortcuts tab. "keyboardnav", // The default keyboard navigation tab. ], content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", init_instance_callback: function (editor) { // Set textbox id for accessibility purposes (label htmlFor). setEditorId(editor.id); }, }} /> <button onClick={handleContentSubmit} style={{ margin: "16px" }}> Save content </button> </> ); }; export default TextEditor;

This component provides a rich text editing experience, with features like advanced list handling, anchors, auto links, image handling, link handling, search and replace, tables, and word count, among others. The TinyMCE editor is highly customizable and allows you to enable features as needed by importing the corresponding plugins and adding them to the plugins array in the init prop of the Editor component.

If you want the editor component to be controlled (i.e., its state is managed by the parent component), you can uncomment the onEditorChange prop and its corresponding handler function.

Important: Remember to replace "gpl" with your actual TinyMCE license key if you intend to use TinyMCE for commercial purposes. You can read about TinyMCE license keys here

With the TextEditor component in place, let's replace the code in src/App.tsx with:

src/App.tsx

import "./App.css"; import TextEditor from "./components/TextEditor"; function App() { return ( <> <h1>Text Editor</h1> <TextEditor /> </> ); } export default App;

You can now run npm run dev and open http://localhost:5173/ in your browser to see the text editor in action. Start editing away!

The source code covered in this guide is available in my GitHub repository. Remember that you can always contact me for questions or comments.

Share:
Questions or comments?

Was this helpful?

If this content added value to your day, consider fueling my late-night coding sessions! Your support helps me keep creating and sharing more geeky goodness.

Buy me a coffeeBuy me a coffee