React setup

Redactor is a plain JavaScript editor. In React you import it, mount it on a DOM node, keep the returned instance, and call destroy() on unmount.

This guide uses the ES module build (redactor.esm.min.js) with Vite, Create React App, or any bundler.

Two patterns:

  1. Redactor object — import the editor (and plugins), then call Redactor(element, options).
  2. React component — wrap that call in useEffect and destroy() on a host ref.

Copy files#

Unpack the Redactor ZIP and copy the package files into the project, for example:

src/vendor/redactor/
  redactor.esm.min.js
  redactor.css
  plugins/
    counter/
      counter.js

Adjust paths below to match your folder.

Redactor object#

The simplest case: no React component yet — import Redactor (and any plugins), then call it on a host element already in the HTML.

// main.jsx
import './vendor/redactor/redactor.css';
import Redactor from './vendor/redactor/redactor.esm.min.js';
import './vendor/redactor/plugins/counter.js';

const editor = Redactor('#editor', {
    content: '<p>Hello from React</p>',
    plugins: ['counter']
});

The page only needs a host node and your module entry:

<!-- index.html -->
<!doctype html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React App</title>
</head>
<body>
    <div id="editor"></div>
    <script type="module" src="/src/main.jsx"></script>
</body>
</html>

Component#

In a real React app you wrap the same call in a component: mount the editor in useEffect when the host ref is ready, and call destroy() in the effect cleanup.

Mount the app as usual:

// main.jsx
import { createRoot } from 'react-dom/client';
import RedactorEditor from './RedactorEditor';

createRoot(document.getElementById('root')).render(<RedactorEditor />);

The editor component keeps a ref to an empty div, creates Redactor once on mount, and tears it down on unmount:

// RedactorEditor.jsx
import { useEffect, useRef } from 'react';
import Redactor from './vendor/redactor/redactor.esm.min.js';
import './vendor/redactor/redactor.css';
import './vendor/redactor/plugins/counter.js';

export default function RedactorEditor() {
    const hostRef = useRef(null);

    useEffect(() => {
        const el = hostRef.current;
        if (!el) return;

        const editor = Redactor(el, {
            content: '<p>Hello World!</p>',
            plugins: ['counter']
        });

        return () => {
            editor.destroy();
        };
    }, []);

    return <div ref={hostRef} />;
}

index.html only needs the React root — the host element lives inside the component:

<!-- index.html -->
<!doctype html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React</title>
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
</body>
</html>

Notes

  • Pass the element (hostRef.current), not only a CSS selector — the ref is already the host node.
  • Redactor() returns one editor instance. Call destroy() on unmount.
  • Import redactor.css once (in the component or main.jsx).
  • Do not call Redactor() during render — only in useEffect after the host exists.
  • Import plugins with a static import so they register before useEffect runs (including under React Strict Mode).

Read and update content#

const html = editor.getHtml();
editor.setContent('<p>Updated</p>');

For controlled forms, read HTML on submit from getHtml() instead of syncing on every keystroke.

Multi-page app#

Destroy the instance when leaving a page:

let editor;

function Home() {
    useEffect(() => {
        editor = Redactor('#editor', {
            plugins: ['counter']
        });

        return () => {
            editor?.destroy();
            editor = null;
        };
    }, []);

    return (
        <div>
            <h2>Home</h2>
            <div id="editor"></div>
        </div>
    );
}

function About() {
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

Prefer cleanup inside useEffect (as above) so React Strict Mode and route changes always tear the editor down cleanly.

TypeScript#

Redactor ships without official TypeScript types. Use a minimal declaration:

// src/vendor/redactor/redactor.d.ts
declare module '*redactor.esm.min.js' {
    const Redactor: any;
    export default Redactor;
}