Integrating TinyMCE rich text editor with React
4 min read
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.