From c602f7228306ac8b0f31af45627cc3955add83d3 Mon Sep 17 00:00:00 2001 From: Rui Serra Date: Wed, 19 Oct 2022 19:23:51 -0700 Subject: [PATCH] Fix: flush EditorContent state changes immediately once initialized This fixes the cursor problem described in tiptap#3200 Node views need to be rendered immediately when they're created so that the editor can correctly position the cursor. That's achieved using `flushSync` whenever a new node view renderer is added. However, `flushSync` cannot be used from inside a React component lifecycle method. By keeping an instance variable to determine if initialization has happened, we can avoid using `flushSync` from inside the `componentDidMount` and `componentDidUpdate` methods, and still call it whenever a new node view is created afterwards. --- packages/react/src/EditorContent.tsx | 45 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/react/src/EditorContent.tsx b/packages/react/src/EditorContent.tsx index c39abb4c54..02a398ef37 100644 --- a/packages/react/src/EditorContent.tsx +++ b/packages/react/src/EditorContent.tsx @@ -29,9 +29,12 @@ export interface EditorContentState { export class PureEditorContent extends React.Component { editorContentRef: React.RefObject + initialized: boolean + constructor(props: EditorContentProps) { super(props) this.editorContentRef = React.createRef() + this.initialized = false this.state = { renderers: {}, @@ -65,32 +68,42 @@ export class PureEditorContent extends React.Component void) { + // Avoid calling flushSync until the editor is initialized. + // Initialization happens during the componentDidMount or componentDidUpdate + // lifecycle methods, and React doesn't allow calling flushSync from inside + // a lifecycle method. + if (this.initialized) { + flushSync(fn) + } else { + fn() } } setRenderer(id: string, renderer: ReactRenderer) { - queueMicrotask(() => { - flushSync(() => { - this.setState(({ renderers }) => ({ - renderers: { - ...renderers, - [id]: renderer, - }, - })) - }) + this.maybeFlushSync(() => { + this.setState(({ renderers }) => ({ + renderers: { + ...renderers, + [id]: renderer, + }, + })) }) } removeRenderer(id: string) { - queueMicrotask(() => { - flushSync(() => { - this.setState(({ renderers }) => { - const nextRenderers = { ...renderers } + this.maybeFlushSync(() => { + this.setState(({ renderers }) => { + const nextRenderers = { ...renderers } - delete nextRenderers[id] + delete nextRenderers[id] - return { renderers: nextRenderers } - }) + return { renderers: nextRenderers } }) }) }