Skip to content

Fix RichTextInput crash when the editor is recreated#11309

Open
quentin-decre wants to merge 1 commit into
marmelab:masterfrom
quentin-decre:fix/rich-text-input-destroyed-editor
Open

Fix RichTextInput crash when the editor is recreated#11309
quentin-decre wants to merge 1 commit into
marmelab:masterfrom
quentin-decre:fix/rich-text-input-destroyed-editor

Conversation

@quentin-decre

Copy link
Copy Markdown
Contributor

Problem

Under tiptap v3, <RichTextInput> can crash with:

TypeError: Cannot read properties of null (reading 'commands')
    at get commands (@tiptap/core)
    at RichTextInput (content-sync useEffect)
    at commitHookEffectListMount (react-dom)

It happens when the editor is recreated — e.g. toggling readOnly/disabled, or changing editorOptions/id — while a record/value change is in flight. In our app it reproduces reliably when switching between records/replies (which flips readOnly and changes the content at the same time).

Root cause

tiptap v3's Editor.destroy() sets this.commandManager = null, and the commands getter returns this.commandManager.commands. The content-sync effect guards only if (!editor) return, so when useEditor recreates the editor, the old (now destroyed) instance can still be referenced by this passive effect, which then calls editor.commands.setContent(...) on it → dereference of a null commandManager.

useEffect(() => {
    if (!editor) return; // truthy but destroyed slips through
    const { from, to } = editor.state.selection;
    editor.commands.setContent(field.value, { ... }); // 💥 commandManager is null
    editor.commands.setTextSelection({ from, to });
}, [editor, field.value]);

Fix

Also bail out when the editor is destroyed:

if (!editor || editor.isDestroyed) return;

editor.isDestroyed returns true once destroy() has run (it reads editorView?.isDestroyed ?? true), and destroy() nulls commandManager in the same call, so this reliably prevents the dereference. It mirrors the existing !editor guard and is a no-op in the normal path.

Notes

  • Reproduced on ra-input-rich-text@5.15.1 with @tiptap/*@3 (package declares @tiptap/* ^3.20.4).
  • The crash is timing-dependent (editor recreation + passive-effect ordering), so I didn't add a deterministic jsdom test — happy to add one if you can point me at the preferred way to simulate editor recreation in the existing test harness.

Tiptap v3's Editor.destroy() sets commandManager to null and the commands
getter dereferences it. When the editor is recreated (readOnly/disabled/
editorOptions/id change), the content-sync passive effect could run against
the just-destroyed instance and throw "Cannot read properties of null
(reading 'commands')". Skip destroyed editors in addition to null ones.
@fzaninotto

Copy link
Copy Markdown
Member

Thanks for the contribution. If the bug is timing-dependent, would you mind adding a story allowing to reproduce the bug manually?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants