Manage React editor state
Use one owner for the current document. Choose controlled state when the rest of your application needs each change; choose an uncontrolled editor when you mainly need to read content at Save. These examples use the npm React binding in HTML mode.
Controlled: keep local state current
"use client";
import { useState } from "react";
import { RichTextEditor } from "@richscripts/richtexteditor/react";
export default function Composer() {
const [html, setHtml] = useState("<p>Start writing...</p>");
return <RichTextEditor
value={html}
onChange={(next) => {
if (typeof next === "string") setHtml(next);
}}
/>;
}Keep the value emitted by the editor as your local editing value. Debounce network persistence separately, rather than deliberately feeding an older value back into the editor. Avoid replacing the whole document with a server-normalized copy after every save.
The binding distinguishes its own emitted values from external replacements. It does not make every application transformation selection-safe. An onChange handler that reformats HTML and sends different content back is asking the editor to replace content while someone is typing.
Uncontrolled: read the current HTML at Save
"use client";
import { useRef, useState } from "react";
import { RichTextEditor } from "@richscripts/richtexteditor/react";
import type { RichTextEditorReactHandle } from "@richscripts/richtexteditor/react";
export default function Draft({ initialHtml, onSave }: {
initialHtml: string;
onSave: (html: string) => void;
}) {
const editorRef = useRef<RichTextEditorReactHandle>(null);
const [ready, setReady] = useState(false);
return <>
<RichTextEditor ref={editorRef} defaultValue={initialHtml}
onReady={() => setReady(true)} />
<button type="button" disabled={!ready} onClick={() => {
if (editorRef.current?.getEditor()) {
onSave(editorRef.current.getHTMLCode());
}
}}>Save</button>
</>;
}defaultValue seeds the document; it is not a way to push subsequent changes into a mounted editor. The ref exposes getHTMLCode(). The readiness check prevents a premature Save from treating an editor that has not initialized as an empty document.
This example hands HTML to your application callback; it does not implement persistence, upload completion or save-error recovery. If images are enabled, use the upload-and-save sequence before reading final HTML. Validate and sanitize submitted content on the server.
Switch documents and handle server responses safely
- Use a stable document identity. A
keybased on the current HTML or a random number remounts the editor during editing. - A deliberate document-ID key can start a fresh editor for a different document. Confirm or save a dirty draft before switching; a remount discards instance state such as selection and undo history.
- If you load documents asynchronously, ignore or abort a response for a document that is no longer selected.
- Track a document revision when saving. A late response should update save status for its own revision, not overwrite a newer draft. Server-side revision checks are needed to detect another user’s conflicting save.
- Do not switch between controlled and uncontrolled modes mid-session. Choose the pattern when opening the document.
Mounting and Strict Mode
Use the supplied React wrapper rather than creating a second editor on the same host. If you write your own wrapper, create the editor after the host exists, detach owned listeners and destroy the owned instance on cleanup. Ignore asynchronous initialization that finishes after unmount.
React Strict Mode can run an extra setup-and-cleanup cycle in development. Keep it enabled while checking lifecycle behavior. For Next.js, follow the client-component integration guide; rendering saved content and mounting an interactive editor are separate concerns.
Troubleshooting checklist
- Cursor jumps: check changing keys, recreated components, repeated
setHTMLCode()calls and transformed controlled values. - Typing disappears: check stale values from delayed callbacks, save responses and document loads.
- Save misses the last edit: read the editor ref at submission rather than assuming a delayed change notification has already updated React state.
- Duplicate toolbars: check duplicate initialization and cleanup, including route changes and modal reopen.
- Large drafts feel slow: profile HTML serialization, preview rendering and network saves independently.
- IME input breaks: test Chinese or Japanese composition without rewriting content during composition, and check undo after the text is committed.
Next: diagnose cursor jumps step by step, install the React binding, or try form submission and save and load.
References: React Strict Mode and preserving and resetting state.