tiptap.ts1.7 KBView on GitHub /**
* `editor` is not a sufficient liveness check.
*
* TipTap's `useEditor` does not destroy an editor when its component unmounts — it schedules
* the destroy on a 1ms timer (`EditorInstanceManager.scheduleDestroy`), so the teardown lands
* a tick later, after React has already finished the commit. And a destroyed editor is NOT
* null: it keeps its identity while `commandManager`, `view` and `state` are nulled out
* underneath it.
*
* So `if (!editor) return` is a half-guard. Anything still holding the reference when the
* destroy lands — an effect re-running because a query resolved mid-teardown, a debounce
* timer, a callback resuming after `await`, a parent that stashed the editor from
* `onEditorCreated` — reaches a live-LOOKING object and throws on the first property it
* touches: `Cannot read properties of null (reading 'commands')` from `editor.commands`,
* `(reading 'nodes')` from `editor.getText()`. Those two were the top production errors,
* and because they throw from render/effect they escape to the route error boundary and
* take the whole page down rather than failing quietly.
*
* Use this anywhere the editor is touched outside a direct user interaction on a mounted
* component: effects, timers, microtasks, and anything after an `await`.
*
* liveEditor(editor)?.commands.setContent(markdown);
*
* Structurally typed rather than tied to `Editor` so it works for both the `@tiptap/react`
* and `@tiptap/core` editor types, and for the handles components expose over them.
*/
export function liveEditor<T extends { isDestroyed: boolean }>(
editor: T | null | undefined,
): T | null {
return editor && !editor.isDestroyed ? editor : null;
}