Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/core/src/editor/BlockNoteEditor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ it("block prop types", () => {
}
});

it("onMount and onUnmount", () => {
it("onMount and onUnmount", async () => {
const editor = BlockNoteEditor.create();
let mounted = false;
let unmounted = false;
Expand All @@ -118,6 +118,10 @@ it("onMount and onUnmount", () => {
expect(mounted).toBe(true);
expect(unmounted).toBe(false);
editor.unmount();
// expect the unmount event to not have been triggered yet, since it waits 2 ticks
expect(unmounted).toBe(false);
// wait 3 ticks to ensure the unmount event is triggered
await new Promise((resolve) => setTimeout(resolve, 3));
expect(mounted).toBe(true);
expect(unmounted).toBe(true);
});
Expand Down
31 changes: 28 additions & 3 deletions packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1035,15 +1035,40 @@ export class BlockNoteEditor<
* @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting
*/
public mount = (element: HTMLElement) => {
// TODO: Fix typing for this in a TipTap PR
this._tiptapEditor.mount({ mount: element } as any);
if (
// If the editor is scheduled for destruction, and
this.scheduledDestructionTimeout &&
// If the editor is being remounted to the same element as the one which is scheduled for destruction,
// then just cancel the destruction timeout
this.prosemirrorView.dom === element
) {
clearTimeout(this.scheduledDestructionTimeout);
this.scheduledDestructionTimeout = undefined;
return;
}

this._tiptapEditor.mount({ mount: element });
};

/**
* Timeout to schedule the {@link unmount}ing of the editor.
*/
private scheduledDestructionTimeout:
| ReturnType<typeof setTimeout>
| undefined = undefined;

/**
* Unmount the editor from the DOM element it is bound to
*/
public unmount = () => {
this._tiptapEditor.unmount();
// Due to how React's StrictMode works, it will `unmount` & `mount` the component twice in development mode.
// This can result in the editor being unmounted mid-rendering the content of node views.
// To avoid this, we only ever schedule the `unmount`ing of the editor when we've seen whether React "meant" to actually unmount the editor (i.e. not calling mount one tick later).
// So, we wait two ticks to see if the component is still meant to be unmounted, and if not, we actually unmount the editor.
this.scheduledDestructionTimeout = setTimeout(() => {
this._tiptapEditor.unmount();
this.scheduledDestructionTimeout = undefined;
}, 1);
};

/**
Expand Down
1 change: 1 addition & 0 deletions packages/server-util/src/context/ServerBlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ export class ServerBlockNoteEditor<
return await fn();
} finally {
tmpRoot.unmount();
await new Promise((resolve) => setTimeout(resolve, 3));
}
});
}
Expand Down
Loading
Loading