fix(react): attempt to cleanup editor instances, on creation #5492 (#5496)

The core of the change ended up being quite simple, because we can create the editor within the first render, we need to already schedule it's destruction.
Scheduling a destruction, ensures that an instance that was created in that first render pass can be cleaned up.
Waiting one more tick than before ensures that we don't accidentally destroy an editor instance that could actually be valid in the next render pass.

In StrictMode, there will be two editor instances created, the first will be created & quickly destroyed in 2 ticks.
In Normal React, there will only ever be 1 instance created and destroyed only on unmount.
This commit is contained in:
Nick Perez 2024-08-15 08:55:42 +02:00 committed by GitHub
parent 3f5dbbbaad
commit 6a0f4f30f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 20 additions and 4 deletions

View File

@ -0,0 +1,5 @@
---
"@tiptap/react": patch
---
Resolves a bug where `useEditor` may not properly cleanup an instance created when in React's StrictMode #5492

View File

@ -14,7 +14,7 @@ import * as Y from 'yjs'
const ydoc = new Y.Doc()
const provider = new WebrtcProvider('tiptap-collaboration-cursor-extension', ydoc)
export default () => {
function Component() {
const editor = useEditor({
extensions: [
Document,
@ -39,3 +39,11 @@ export default () => {
return <EditorContent editor={editor} />
}
function App() {
const useStrictMode = true
return useStrictMode ? <React.StrictMode><Component /></React.StrictMode> : <Component />
}
export default App

View File

@ -78,6 +78,7 @@ class EditorInstanceManager {
this.options = options
this.subscriptions = new Set<() => void>()
this.setEditor(this.getInitialEditor())
this.scheduleDestroy()
this.getEditor = this.getEditor.bind(this)
this.getServerSnapshot = this.getServerSnapshot.bind(this)
@ -253,10 +254,10 @@ class EditorInstanceManager {
const currentInstanceId = this.instanceId
const currentEditor = this.editor
// Wait a tick to see if the component is still mounted
// Wait two ticks to see if the component is still mounted
this.scheduledDestructionTimeout = setTimeout(() => {
if (this.isComponentMounted && this.instanceId === currentInstanceId) {
// If still mounted on the next tick, with the same instanceId, do not destroy the editor
// If still mounted on the following tick, with the same instanceId, do not destroy the editor
if (currentEditor) {
// just re-apply options as they might have changed
currentEditor.setOptions(this.options.current)
@ -269,7 +270,9 @@ class EditorInstanceManager {
this.setEditor(null)
}
}
}, 0)
// This allows the effect to run again between ticks
// which may save us from having to re-create the editor
}, 1)
}
}