diff --git a/.yarn/versions/b0eed05a.yml b/.yarn/versions/b0eed05a.yml
new file mode 100644
index 0000000..766dd55
--- /dev/null
+++ b/.yarn/versions/b0eed05a.yml
@@ -0,0 +1,2 @@
+releases:
+ "@handlewithcare/react-codemirror": patch
diff --git a/demo/main.tsx b/demo/main.tsx
index d1f2edb..df0e1df 100644
--- a/demo/main.tsx
+++ b/demo/main.tsx
@@ -139,7 +139,16 @@ function DemoEditor() {
dispatchTransactions={dispatchTransactions}
extensions={extensions}
>
-
+
+
+
+
diff --git a/package.json b/package.json
index f9173eb..0a6f038 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,7 @@
"peerDependencies": {
"@codemirror/state": "*",
"@codemirror/view": "*",
- "react": ">=17 <=19",
- "react-dom": ">=17 <=19"
+ "react": ">=18 <=19",
+ "react-dom": ">=18 <=19"
}
}
diff --git a/src/hooks/useEditor.ts b/src/hooks/useEditor.ts
index 7a3cfbe..19bd234 100644
--- a/src/hooks/useEditor.ts
+++ b/src/hooks/useEditor.ts
@@ -1,9 +1,12 @@
import { EditorState, type Transaction } from "@codemirror/state";
import { EditorView, type EditorViewConfig } from "@codemirror/view";
-import { useLayoutEffect, useRef, useState } from "react";
+import { useRef, useState } from "react";
import { tracking } from "../extensions/tracking.js";
+import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
+import { useEffectEvent } from "./useEffectEvent.js";
+
export type UseViewOptions = Omit & {
defaultState?: EditorState;
};
@@ -68,35 +71,37 @@ export function useEditor(
const [view, setView] = useState(null);
- useLayoutEffect(() => {
+ const createEditorView = useEffectEvent((parent: HTMLDivElement | null) => {
+ if (parent) {
+ return new EditorView({
+ parent,
+ ...config,
+ });
+ }
+ return null;
+ });
+
+ useClientLayoutEffect(() => {
+ const view = createEditorView(parent);
+ setView(view);
+
return () => {
view?.destroy();
};
- }, [view]);
+ }, [parent, createEditorView]);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- useLayoutEffect(() => {
+ useClientLayoutEffect(() => {
dispatchTransactionsRef.current = dispatchTransactions;
- if (!parent) {
- setView(null);
- return;
- }
- if (!view || view.dom.parentElement !== parent) {
- const newView = new EditorView({
- parent,
- ...config,
- });
- setView(newView);
- return;
- }
-
const trs = state.field(tracking);
const newTrs = trs.filter((tr) => !seen.current.has(tr));
- if (newTrs.length) {
+ if (view && newTrs.length) {
view.update(newTrs);
newTrs.forEach((tr) => seen.current.add(tr));
+ } else if (view && view.state.doc.toString() !== state.doc.toString()) {
+ view.setState(state);
+ seen.current = new Set(trs);
}
});
diff --git a/src/hooks/useEffectEvent.ts b/src/hooks/useEffectEvent.ts
new file mode 100644
index 0000000..d11c037
--- /dev/null
+++ b/src/hooks/useEffectEvent.ts
@@ -0,0 +1,16 @@
+import { useCallback, useInsertionEffect, useRef } from "react";
+
+export function useEffectEvent(
+ fn: (...args: P) => R,
+): (...funcArgs: P) => R {
+ const ref = useRef(fn);
+
+ useInsertionEffect(() => {
+ ref.current = fn;
+ }, [fn]);
+
+ return useCallback((...args: P): R => {
+ const f = ref.current;
+ return f(...args);
+ }, []);
+}
diff --git a/yarn.lock b/yarn.lock
index c93d54b..e4cd981 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -647,8 +647,8 @@ __metadata:
peerDependencies:
"@codemirror/state": "*"
"@codemirror/view": "*"
- react: ">=17 <=19"
- react-dom: ">=17 <=19"
+ react: ">=18 <=19"
+ react-dom: ">=18 <=19"
languageName: unknown
linkType: soft