diff --git a/src/hooks/useEditorView.ts b/src/hooks/useEditorView.ts index 5373210e..c3cfaa8d 100644 --- a/src/hooks/useEditorView.ts +++ b/src/hooks/useEditorView.ts @@ -1,18 +1,23 @@ import type { EditorState, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import type { DirectEditorProps } from "prosemirror-view"; -import { useLayoutEffect, useState } from "react"; +import { useLayoutEffect, useRef, useState } from "react"; import { unstable_batchedUpdates as batch } from "react-dom"; import { useForceUpdate } from "./useForceUpdate.js"; -function withBatchedUpdates( - fn: (this: This, ...args: T) => void +function withConditionalFlushUpdates( + fn: (this: This, ...args: T) => void, + view: EditorView | null ): (...args: T) => void { return function (this: This, ...args: T) { - batch(() => { + if (view?.composing) { + batch(() => { + fn.call(this, ...args); + }); + } else { fn.call(this, ...args); - }); + } }; } @@ -42,9 +47,10 @@ export type EditorProps = Omit & EditorStateProps; * the Editor View unmodified after we upgrade to React 18, which batches every * update by default. */ -function withBatchedDispatch( +function withConditionalFlushDispatch( props: EditorProps, - forceUpdate: () => void + forceUpdate: () => void, + view: EditorView | null ): EditorProps & { dispatchTransaction: EditorView["dispatch"]; } { @@ -55,10 +61,11 @@ function withBatchedDispatch( this: EditorView, tr: Transaction ) { - const batchedDispatchTransaction = withBatchedUpdates( - props.dispatchTransaction ?? defaultDispatchTransaction + const conditionallyFlushedDispatch = withConditionalFlushUpdates( + props.dispatchTransaction ?? defaultDispatchTransaction, + view ); - batchedDispatchTransaction.call(this, tr); + conditionallyFlushedDispatch.call(this, tr); forceUpdate(); }, }, @@ -79,20 +86,22 @@ export function useEditorView( props: EditorProps ): EditorView | null { const [view, setView] = useState(null); + const editorPropsRef = useRef(props); const forceUpdate = useForceUpdate(); - const editorProps = withBatchedDispatch(props, forceUpdate); - - const stateProp = "state" in editorProps ? editorProps.state : undefined; + const stateProp = + "state" in editorPropsRef.current + ? editorPropsRef.current.state + : undefined; const state = - "defaultState" in editorProps - ? editorProps.defaultState - : editorProps.state; + "defaultState" in editorPropsRef.current + ? editorPropsRef.current.defaultState + : editorPropsRef.current.state; const nonStateProps = Object.fromEntries( - Object.entries(editorProps).filter( + Object.entries(editorPropsRef.current).filter( ([propName]) => propName !== "state" && propName !== "defaultState" ) ); @@ -120,14 +129,14 @@ export function useEditorView( new EditorView( { mount }, { - ...editorProps, + ...editorPropsRef.current, state, } ) ); return; } - }, [editorProps, mount, state, view]); + }, [props, mount, state, view]); useLayoutEffect(() => { view?.setProps(nonStateProps); @@ -137,5 +146,13 @@ export function useEditorView( if (stateProp) view?.setProps({ state: stateProp }); }, [view, stateProp]); + useLayoutEffect(() => { + editorPropsRef.current = withConditionalFlushDispatch( + props, + forceUpdate, + view + ); + }, [props, view]); + return view; }