Skip to content

Commit 8838e57

Browse files
authored
Merge pull request #7120 from ueberdosis/hotfix/bubble-menu-useeffect-dependencies-cherrypick
Cherry-pick: Prevent bubble menu plugin from reloading every time the dependencies change
2 parents d4363e3 + f0b3552 commit 8838e57

File tree

2 files changed

+42
-29
lines changed

2 files changed

+42
-29
lines changed

.changeset/two-moles-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tiptap/react': patch
3+
---
4+
5+
Prevent Bubble Menu plugin from re-loading every time the BubbleMenu component re-renders. Reverts a regression introduced in v3.6.3, in PR #7028.

packages/react/src/menus/BubbleMenu.tsx

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,55 +34,63 @@ export const BubbleMenu = React.forwardRef<HTMLDivElement, BubbleMenuProps>(
3434

3535
const { editor: currentEditor } = useCurrentEditor()
3636

37-
useEffect(() => {
38-
const bubbleMenuElement = menuEl.current
39-
bubbleMenuElement.style.visibility = 'hidden'
40-
bubbleMenuElement.style.position = 'absolute'
37+
/**
38+
* The editor instance where the bubble menu plugin will be registered.
39+
*/
40+
const pluginEditor = editor || currentEditor
41+
42+
// Creating a useMemo would be more computationally expensive than just
43+
// re-creating this object on every render.
44+
const bubbleMenuPluginProps: Omit<BubbleMenuPluginProps, 'editor' | 'element'> = {
45+
updateDelay,
46+
resizeDelay,
47+
appendTo,
48+
pluginKey,
49+
shouldShow,
50+
getReferencedVirtualElement,
51+
options,
52+
}
53+
/**
54+
* The props for the bubble menu plugin. They are accessed inside a ref to
55+
* avoid running the useEffect hook and re-registering the plugin when the
56+
* props change.
57+
*/
58+
const bubbleMenuPluginPropsRef = useRef(bubbleMenuPluginProps)
59+
bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps
4160

42-
if (editor?.isDestroyed || (currentEditor as any)?.isDestroyed) {
61+
useEffect(() => {
62+
if (pluginEditor?.isDestroyed) {
4363
return
4464
}
4565

46-
const attachToEditor = editor || currentEditor
47-
48-
if (!attachToEditor) {
66+
if (!pluginEditor) {
4967
console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.')
5068
return
5169
}
5270

71+
const bubbleMenuElement = menuEl.current
72+
bubbleMenuElement.style.visibility = 'hidden'
73+
bubbleMenuElement.style.position = 'absolute'
74+
5375
const plugin = BubbleMenuPlugin({
54-
updateDelay,
55-
resizeDelay,
56-
editor: attachToEditor,
76+
...bubbleMenuPluginPropsRef.current,
77+
editor: pluginEditor,
5778
element: bubbleMenuElement,
58-
appendTo,
59-
pluginKey,
60-
shouldShow,
61-
getReferencedVirtualElement,
62-
options,
6379
})
6480

65-
attachToEditor.registerPlugin(plugin)
81+
pluginEditor.registerPlugin(plugin)
82+
83+
const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey
6684

6785
return () => {
68-
attachToEditor.unregisterPlugin(pluginKey)
86+
pluginEditor.unregisterPlugin(createdPluginKey)
6987
window.requestAnimationFrame(() => {
7088
if (bubbleMenuElement.parentNode) {
7189
bubbleMenuElement.parentNode.removeChild(bubbleMenuElement)
7290
}
7391
})
7492
}
75-
}, [
76-
editor,
77-
currentEditor,
78-
pluginKey,
79-
updateDelay,
80-
resizeDelay,
81-
appendTo,
82-
shouldShow,
83-
getReferencedVirtualElement,
84-
options,
85-
])
93+
}, [pluginEditor])
8694

8795
return createPortal(<div {...restProps}>{children}</div>, menuEl.current)
8896
},

0 commit comments

Comments
 (0)