diff --git a/demos/src/Examples/Default/React/index.jsx b/demos/src/Examples/Default/React/index.jsx
index 00c9f575486..18d22551932 100644
--- a/demos/src/Examples/Default/React/index.jsx
+++ b/demos/src/Examples/Default/React/index.jsx
@@ -228,6 +228,6 @@ display: none;
export default () => {
return (
- } extensions={extensions} content={content}>
+ } extensions={extensions} content={content} useImmediateRender>
)
}
diff --git a/docs/api/utilities.md b/docs/api/utilities.md
new file mode 100644
index 00000000000..d4d98288239
--- /dev/null
+++ b/docs/api/utilities.md
@@ -0,0 +1,11 @@
+# Editor API Utility Overview
+
+Welcome to the Editor API Utility section. Here, you'll discover essential tools to enhance your Tiptap experience:
+
+- **Render JSON as HTML**: Learn to convert JSON content to HTML, even without an editor instance, simplifying content management.
+
+- **Tiptap for PHP**: Explore PHP integration for Tiptap, enabling seamless content transformation and modification.
+
+- **Suggestions**: Enhance your editor with suggestions like mentions and emojis, tailored to your needs.
+
+Explore subpages for in-depth guidance and examples.
diff --git a/docs/links.yaml b/docs/links.yaml
index 947f61b4bef..68b97a7996b 100644
--- a/docs/links.yaml
+++ b/docs/links.yaml
@@ -333,8 +333,7 @@
link: /api/extensions/unique-id
type: pro
- title: Utilities
- link: /utilities
- redirect: /api/utilities/html
+ link: /api/utilities
items:
- title: HTML
link: /api/utilities/html
diff --git a/packages/react/src/Context.tsx b/packages/react/src/Context.tsx
index 7a47ed354f6..b15ed9bbdf9 100644
--- a/packages/react/src/Context.tsx
+++ b/packages/react/src/Context.tsx
@@ -3,7 +3,7 @@ import React, { createContext, ReactNode, useContext } from 'react'
import { Editor } from './Editor.js'
import { EditorContent } from './EditorContent.js'
-import { useEditor } from './useEditor.js'
+import { useEditor, useEditorForImmediateRender } from './useEditor.js'
export type EditorContextValue = {
editor: Editor | null;
@@ -19,18 +19,38 @@ export const useCurrentEditor = () => useContext(EditorContext)
export type EditorProviderProps = {
children: ReactNode;
+ /**
+ * This option will create and immediately return a defined editor instance. The editor returned in the context consumer will never be null if
+ * this is enabled. In future major versions, this property will be removed and this behavior will be the defualt.
+ */
+ useImmediateRender?: boolean;
slotBefore?: ReactNode;
slotAfter?: ReactNode;
} & Partial
-export const EditorProvider = ({
+const EditorProviderNoImmediateRender = ({
children, slotAfter, slotBefore, ...editorOptions
-}: EditorProviderProps) => {
+}: Omit) => {
const editor = useEditor(editorOptions)
- if (!editor) {
- return null
- }
+ return (
+
+ {slotBefore}
+
+ {({ editor: currentEditor }) => (
+
+ )}
+
+ {children}
+ {slotAfter}
+
+ )
+}
+
+const EditorProviderImmediateRender = ({
+ children, slotAfter, slotBefore, ...editorOptions
+}: Omit) => {
+ const editor = useEditorForImmediateRender(editorOptions)
return (
@@ -45,3 +65,15 @@ export const EditorProvider = ({
)
}
+
+export const EditorProvider = ({ useImmediateRender, ...providerOptions }: EditorProviderProps) => {
+ if (useImmediateRender) {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+}
diff --git a/packages/react/src/useEditor.ts b/packages/react/src/useEditor.ts
index e80ca4a49a3..6fa8e110d6e 100644
--- a/packages/react/src/useEditor.ts
+++ b/packages/react/src/useEditor.ts
@@ -2,6 +2,7 @@ import { EditorOptions } from '@tiptap/core'
import {
DependencyList,
useEffect,
+ useMemo,
useRef,
useState,
} from 'react'
@@ -113,14 +114,130 @@ export const useEditor = (options: Partial = {}, deps: Dependency
return () => {
isMounted = false
+ editorRef.current?.destroy()
}
}, deps)
+ return editorRef.current
+}
+
+/**
+ * This hook will create and immediately return a defined editor instance. In future major versions, this
+ * hook will be removed and useEditor will be changed to behave like this hook.
+ */
+export const useEditorForImmediateRender = (options: Partial = {}, deps: DependencyList = []) => {
+ const {
+ onBeforeCreate,
+ onBlur,
+ onCreate,
+ onDestroy,
+ onFocus,
+ onSelectionUpdate,
+ onTransaction,
+ onUpdate,
+ } = options
+
+ const onBeforeCreateRef = useRef(onBeforeCreate)
+ const onBlurRef = useRef(onBlur)
+ const onCreateRef = useRef(onCreate)
+ const onDestroyRef = useRef(onDestroy)
+ const onFocusRef = useRef(onFocus)
+ const onSelectionUpdateRef = useRef(onSelectionUpdate)
+ const onTransactionRef = useRef(onTransaction)
+ const onUpdateRef = useRef(onUpdate)
+
+ const isMounted = useRef(false)
+
useEffect(() => {
+ isMounted.current = true
+
return () => {
- return editorRef.current?.destroy()
+ isMounted.current = false
}
}, [])
- return editorRef.current
+ const [, forceUpdate] = useState({})
+ const editor = useMemo(() => {
+ const instance = new Editor(options)
+
+ instance.on('transaction', () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ if (isMounted.current) {
+ forceUpdate({})
+ }
+ })
+ })
+ })
+
+ return instance
+ }, deps)
+
+ useEffect(() => {
+ return () => {
+ editor.destroy()
+ }
+ }, [editor])
+
+ // This effect will handle updating the editor instance
+ // when the event handlers change.
+ useEffect(() => {
+ if (onBeforeCreate) {
+ editor.off('beforeCreate', onBeforeCreateRef.current)
+ editor.on('beforeCreate', onBeforeCreate)
+
+ onBeforeCreateRef.current = onBeforeCreate
+ }
+
+ if (onBlur) {
+ editor.off('blur', onBlurRef.current)
+ editor.on('blur', onBlur)
+
+ onBlurRef.current = onBlur
+ }
+
+ if (onCreate) {
+ editor.off('create', onCreateRef.current)
+ editor.on('create', onCreate)
+
+ onCreateRef.current = onCreate
+ }
+
+ if (onDestroy) {
+ editor.off('destroy', onDestroyRef.current)
+ editor.on('destroy', onDestroy)
+
+ onDestroyRef.current = onDestroy
+ }
+
+ if (onFocus) {
+ editor.off('focus', onFocusRef.current)
+ editor.on('focus', onFocus)
+
+ onFocusRef.current = onFocus
+ }
+
+ if (onSelectionUpdate) {
+ editor.off('selectionUpdate', onSelectionUpdateRef.current)
+ editor.on('selectionUpdate', onSelectionUpdate)
+
+ onSelectionUpdateRef.current = onSelectionUpdate
+ }
+
+ if (onTransaction) {
+ editor.off('transaction', onTransactionRef.current)
+ editor.on('transaction', onTransaction)
+
+ onTransactionRef.current = onTransaction
+ }
+
+ if (onUpdate) {
+ editor.off('update', onUpdateRef.current)
+ editor.on('update', onUpdate)
+
+ onUpdateRef.current = onUpdate
+ }
+ }, [onBeforeCreate, onBlur, onCreate, onDestroy, onFocus, onSelectionUpdate, onTransaction, onUpdate, editor])
+
+ return editor
}