diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
index 76a52d6ac6..ced8f59b14 100644
--- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
+++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
@@ -160,7 +160,7 @@ export async function handleFileInsertion<
insertedBlockId = editor.transact((tr) => {
const posInfo = getNearestBlockPos(tr.doc, pos.pos);
- const blockElement = editor.prosemirrorView.dom.querySelector(
+ const blockElement = editor.domElement?.querySelector(
`[data-id="${posInfo.node.attrs.id}"]`,
);
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
index cfba83cc23..34f53b0119 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
@@ -93,12 +93,12 @@ export const CreateLinkButton = () => {
}
};
- editor.prosemirrorView.dom.addEventListener("keydown", callback);
+ editor.domElement?.addEventListener("keydown", callback);
return () => {
- editor.prosemirrorView.dom.removeEventListener("keydown", callback);
+ editor.domElement?.removeEventListener("keydown", callback);
};
- }, [editor.prosemirrorView, editor.headless]);
+ }, [editor.domElement]);
if (state === undefined) {
return null;
diff --git a/tests/src/unit/react/BlockNoteViewRemount.test.tsx b/tests/src/unit/react/BlockNoteViewRemount.test.tsx
new file mode 100644
index 0000000000..4e0689153c
--- /dev/null
+++ b/tests/src/unit/react/BlockNoteViewRemount.test.tsx
@@ -0,0 +1,72 @@
+import { BlockNoteEditor } from "@blocknote/core";
+import { BlockNoteView } from "@blocknote/mantine";
+import "@blocknote/mantine/style.css";
+import { FormattingToolbar } from "@blocknote/react";
+import { flushSync } from "react-dom";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+
+describe("FormattingToolbar unmount", () => {
+ let div: HTMLDivElement;
+
+ beforeEach(() => {
+ div = document.createElement("div");
+ document.body.appendChild(div);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(div);
+ });
+
+ it("should not throw error when unmounting editor with FormattingToolbar", () => {
+ const editor = BlockNoteEditor.create();
+
+ const root = createRoot(div);
+
+ // Mount the editor with FormattingToolbar
+ // This will cause CreateLinkButton to mount and register its event listeners
+ flushSync(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+
+ // Unmount should not throw error
+ // Before the fix in commit 17bff6362, this would throw:
+ // "Cannot read properties of undefined (reading 'removeEventListener')"
+ // because CreateLinkButton's useEffect cleanup tries to access
+ // editor.prosemirrorView.dom.removeEventListener()
+ // but prosemirrorView.dom is undefined when the editor is being destroyed
+ expect(() => {
+ root.unmount();
+ }).not.toThrow();
+
+ // Cleanup
+ editor._tiptapEditor.destroy();
+ });
+
+ it("should handle rapid mount/unmount cycles", () => {
+ // Test multiple mount/unmount cycles
+ for (let i = 0; i < 3; i++) {
+ const editor = BlockNoteEditor.create();
+ const root = createRoot(div);
+
+ flushSync(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+
+ // Should not throw on unmount
+ expect(() => {
+ root.unmount();
+ }).not.toThrow();
+
+ editor._tiptapEditor.destroy();
+ }
+ });
+});
diff --git a/tests/vite.config.ts b/tests/vite.config.ts
index b76c27de4a..8f03c6efcf 100644
--- a/tests/vite.config.ts
+++ b/tests/vite.config.ts
@@ -1,13 +1,12 @@
import * as path from "path";
import { defineConfig } from "vite";
-import eslintPlugin from "vite-plugin-eslint";
// https://vitejs.dev/config/
export default defineConfig((conf) => ({
test: {
environment: "jsdom",
setupFiles: ["./vitestSetup.ts"],
- include: ["./src/unit/**/*.test.ts"],
+ include: ["./src/unit/**/*.test.ts", "./src/unit/**/*.test.tsx"],
},
resolve: {
alias:
diff --git a/tests/vitestSetup.ts b/tests/vitestSetup.ts
index 9a869521e0..d9fa31cb56 100644
--- a/tests/vitestSetup.ts
+++ b/tests/vitestSetup.ts
@@ -33,3 +33,18 @@ class DragEventMock extends Event {
};
}
(global as any).DragEvent = DragEventMock;
+
+// Mock matchMedia for Mantine
+Object.defineProperty(window, "matchMedia", {
+ writable: true,
+ value: (query: string) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: () => {}, // deprecated
+ removeListener: () => {}, // deprecated
+ addEventListener: () => {},
+ removeEventListener: () => {},
+ dispatchEvent: () => {},
+ }),
+});