Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/smart-humans-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tiptap/react': patch
---

Fixed an error where `flushSync()` would run in `<EditorContent />` lifecycle
5 changes: 4 additions & 1 deletion packages/react/src/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import type { ReactPortal } from 'react'

import type { ReactRenderer } from './ReactRenderer.js'

export type EditorWithContentComponent = Editor & { contentComponent?: ContentComponent | null }
export type EditorWithContentComponent = Editor & {
contentComponent?: ContentComponent | null
isEditorContentInitialized?: boolean
}
export type ContentComponent = {
setRenderer(id: string, renderer: ReactRenderer): void
removeRenderer(id: string): void
Expand Down
39 changes: 4 additions & 35 deletions packages/react/src/EditorContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,9 @@ export class PureEditorContent extends React.Component<
> {
editorContentRef: React.RefObject<any>

initialized: boolean

unsubscribeToContentComponent?: () => void

constructor(props: EditorContentProps) {
super(props)
this.editorContentRef = React.createRef()
this.initialized = false

this.state = {
hasContentComponentInitialized: Boolean((props.editor as EditorWithContentComponent | null)?.contentComponent),
}
}

componentDidMount() {
Expand Down Expand Up @@ -129,29 +120,11 @@ export class PureEditorContent extends React.Component<

editor.contentComponent = getInstance()

// Has the content component been initialized?
if (!this.state.hasContentComponentInitialized) {
// Subscribe to the content component
this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
this.setState(prevState => {
if (!prevState.hasContentComponentInitialized) {
return {
hasContentComponentInitialized: true,
}
}
return prevState
})

// Unsubscribe to previous content component
if (this.unsubscribeToContentComponent) {
this.unsubscribeToContentComponent()
}
})
}

editor.createNodeViews()

this.initialized = true
editor.isEditorContentInitialized = true

this.forceUpdate()
}
}

Expand All @@ -162,18 +135,14 @@ export class PureEditorContent extends React.Component<
return
}

this.initialized = false
editor.isEditorContentInitialized = false

if (!editor.isDestroyed) {
editor.view.setProps({
nodeViews: {},
})
}

if (this.unsubscribeToContentComponent) {
this.unsubscribeToContentComponent()
}

editor.contentComponent = null

// try to reset the editor element
Expand Down
11 changes: 5 additions & 6 deletions packages/react/src/ReactRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ type ComponentType<R, P> =
export class ReactRenderer<R = unknown, P extends Record<string, any> = object> {
id: string

editor: Editor
editor: EditorWithContentComponent

component: any

Expand All @@ -173,7 +173,7 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
) {
this.id = Math.floor(Math.random() * 0xffffffff).toString()
this.component = component
this.editor = editor as EditorWithContentComponent
this.editor = editor
this.props = props as P
this.element = document.createElement(as)
this.element.classList.add('react-renderer')
Expand All @@ -182,10 +182,9 @@ export class ReactRenderer<R = unknown, P extends Record<string, any> = object>
this.element.classList.add(...className.split(' '))
}

// If the editor is already initialized, we will need to
// synchronously render the component to ensure it renders
// together with Prosemirror's rendering.
if (this.editor.isInitialized) {
if (this.editor.isEditorContentInitialized) {
// On first render, we need to flush the render synchronously
// Renders afterwards can be async, but this fixes a cursor positioning issue
flushSync(() => {
this.render()
})
Expand Down
Loading