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:
- Redactor object — import the editor (and plugins), then call
Redactor(element, options). - React component — wrap that call in
useEffectanddestroy()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. Calldestroy()on unmount.- Import
redactor.cssonce (in the component ormain.jsx). - Do not call
Redactor()during render — only inuseEffectafter the host exists. - Import plugins with a static
importso they register beforeuseEffectruns (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;
}