Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify useEditorView to flushSync all dispatched transactions #49

Merged
merged 12 commits into from
Sep 11, 2023
2 changes: 2 additions & 0 deletions .yarn/versions/290b480a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
releases:
"@nytimes/react-prosemirror": minor
27 changes: 7 additions & 20 deletions src/hooks/useEditorView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import type { EditorState, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import type { DirectEditorProps } from "prosemirror-view";
import { useLayoutEffect, useState } from "react";
import { unstable_batchedUpdates as batch } from "react-dom";
import { flushSync } from "react-dom";

import { useForceUpdate } from "./useForceUpdate.js";

function withBatchedUpdates<This, T extends unknown[]>(
function withFlushedUpdates<This, T extends unknown[]>(
fn: (this: This, ...args: T) => void
): (...args: T) => void {
return function (this: This, ...args: T) {
batch(() => {
flushSync(() => {
fn.call(this, ...args);
});
};
Expand All @@ -30,19 +30,7 @@ type EditorStateProps =

export type EditorProps = Omit<DirectEditorProps, "state"> & EditorStateProps;

/**
* Enhances editor props so transactions dispatch in a batched update.
*
* It is important that changes to the editor get batched by React so that any
* components that dispatch transactions in effects do so after rendering with
* state changes from any previous transaction, so that they may use the latest
* state and not trigger nested transactions.
*
* TODO(OK-4006): We can remove this helper and pass the direct editor props to
* the Editor View unmodified after we upgrade to React 18, which batches every
* update by default.
*/
function withBatchedDispatch(
function withFlushedDispatch(
props: EditorProps,
forceUpdate: () => void
): EditorProps & {
Expand All @@ -55,10 +43,10 @@ function withBatchedDispatch(
this: EditorView,
tr: Transaction
) {
const batchedDispatchTransaction = withBatchedUpdates(
const flushedDispatch = withFlushedUpdates(
props.dispatchTransaction ?? defaultDispatchTransaction
);
batchedDispatchTransaction.call(this, tr);
flushedDispatch.call(this, tr);
if (!("state" in props)) forceUpdate();
},
},
Expand All @@ -79,10 +67,9 @@ export function useEditorView<T extends HTMLElement = HTMLElement>(
props: EditorProps
): EditorView | null {
const [view, setView] = useState<EditorView | null>(null);

const forceUpdate = useForceUpdate();

const editorProps = withBatchedDispatch(props, forceUpdate);
const editorProps = withFlushedDispatch(props, forceUpdate);

const stateProp = "state" in editorProps ? editorProps.state : undefined;

Expand Down
Loading