Skip to content

Commit

Permalink
Fix: flush EditorContent state changes immediately once initialized
Browse files Browse the repository at this point in the history
This fixes the cursor problem described in tiptap#3200

Node views need to be rendered immediately when they're created so that
the editor can correctly position the cursor. That's achieved using
`flushSync` whenever a new node view renderer is added.

However, `flushSync` cannot be used from inside a React component
lifecycle method.

By keeping an instance variable to determine if initialization has
happened, we can avoid using `flushSync` from inside the `componentDidMount`
and `componentDidUpdate` methods, and still call it whenever a new node view
is created afterwards.
  • Loading branch information
ruipserra committed Oct 20, 2022
1 parent 893c48d commit c602f72
Showing 1 changed file with 29 additions and 16 deletions.
45 changes: 29 additions & 16 deletions packages/react/src/EditorContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ export interface EditorContentState {
export class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
editorContentRef: React.RefObject<any>

initialized: boolean

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

this.state = {
renderers: {},
Expand Down Expand Up @@ -65,32 +68,42 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
editor.contentComponent = this

editor.createNodeViews()

this.initialized = true
}
}

maybeFlushSync(fn: () => void) {
// Avoid calling flushSync until the editor is initialized.
// Initialization happens during the componentDidMount or componentDidUpdate
// lifecycle methods, and React doesn't allow calling flushSync from inside
// a lifecycle method.
if (this.initialized) {
flushSync(fn)
} else {
fn()
}
}

setRenderer(id: string, renderer: ReactRenderer) {
queueMicrotask(() => {
flushSync(() => {
this.setState(({ renderers }) => ({
renderers: {
...renderers,
[id]: renderer,
},
}))
})
this.maybeFlushSync(() => {
this.setState(({ renderers }) => ({
renderers: {
...renderers,
[id]: renderer,
},
}))
})
}

removeRenderer(id: string) {
queueMicrotask(() => {
flushSync(() => {
this.setState(({ renderers }) => {
const nextRenderers = { ...renderers }
this.maybeFlushSync(() => {
this.setState(({ renderers }) => {
const nextRenderers = { ...renderers }

delete nextRenderers[id]
delete nextRenderers[id]

return { renderers: nextRenderers }
})
return { renderers: nextRenderers }
})
})
}
Expand Down

0 comments on commit c602f72

Please sign in to comment.