Hooks
Blockslides provides React hooks for creating and managing editor instances, subscribing to state changes, and accessing editor context throughout your component tree.
useSlideEditor
The primary hook for initializing a slide editor with the ExtensionKit bundle. This hook handles all the configuration needed for a fully-featured slide editor.
import { useSlideEditor } from '@blockslides/react'
function Editor() {
const { editor, presets } = useSlideEditor({
content: initialContent,
onChange: (doc, editor) => {
console.log('Content changed:', doc)
}
})
return <EditorContent editor={editor} />
}Configuration
const { editor, presets } = useSlideEditor({
// Initial content (defaults to a single blank slide)
content: {
type: 'doc',
content: [/* slides */]
},
// Called on every document update
onChange: (doc, editor) => {
saveToDatabase(doc)
},
// Additional extensions beyond ExtensionKit
extensions: [MyCustomExtension],
// Configure or disable ExtensionKit features
extensionKitOptions: {
bubbleMenu: false,
youtube: { autoplay: false }
},
// Custom preset templates for add-slide button
presetTemplates: customPresets,
// SSR configuration
immediatelyRender: false,
// Theme (applies CSS class)
theme: 'light',
// Lifecycle hooks
onEditorReady: (editor) => {
console.log('Editor initialized')
},
// Dependencies for re-instantiation
deps: []
})Parameters:
- content (
JSONContent) - Initial document content. Omit to start with a default blank slide. - onChange (
(doc: JSONContent, editor: Editor) => void) - Callback fired after each update with the current document JSON. - extensions (
AnyExtension[]) - Additional extensions appended after ExtensionKit. - extensionKitOptions (
ExtensionKitOptions) - Customize ExtensionKit configuration or disable specific extensions. - presetTemplates (
PresetTemplates) - Custom template list for the add-slide button. Defaults to built-in presets. - immediatelyRender (
boolean) - Set tofalsefor SSR to prevent hydration mismatches. - shouldRerenderOnTransaction (
boolean) - Legacy mode that re-renders on every transaction. Defaults tofalse. - theme (
'light' | 'dark') - Theme name applied as CSS class to editor. - editorProps (
EditorProps) - ProseMirror EditorProps for advanced configuration. - deps (
DependencyList) - Dependencies array that triggers editor re-instantiation when changed. Leave empty to prevent recreation. - onEditorReady (
(editor: Editor) => void) - Called once when the editor instance is ready. - onUpdate - Additional update callback (runs alongside
onChange). - onCreate, onBeforeCreate, onDestroy, onFocus, onBlur, onSelectionUpdate, onTransaction - Standard editor lifecycle hooks.
Returns:
- editor (
Editor | null) - The editor instance, ornullduring SSR or initial render withimmediatelyRender: false. - presets - Preset templates list used by the add-slide button.
Server-Side Rendering
For SSR frameworks like Next.js, set immediatelyRender: false to avoid hydration errors:
const { editor } = useSlideEditor({
immediatelyRender: false,
content: initialContent
})
// Editor will be null on the server
if (!editor) {
return <div>Loading editor...</div>
}Managing Updates
The hook provides two ways to capture content changes:
onChange callback:
const { editor } = useSlideEditor({
onChange: (doc, editor) => {
// Receives JSON on every update
autosave(doc)
}
})Manual extraction:
const { editor } = useSlideEditor({
onEditorReady: (editor) => {
editor.on('update', () => {
const json = editor.getJSON()
const html = editor.getHTML()
})
}
})Customizing ExtensionKit
const { editor } = useSlideEditor({
extensionKitOptions: {
youtube: false,
bubbleMenu: false,
fileHandler: {
onDrop: (editor, files, pos) => {
handleFileUpload(files, pos)
}
}
}
})Full Configuration Options
See Extension Kit Overview for all available configuration options.
Adding Custom Extensions
Extend the editor with your own extensions:
import { MyCustomNode } from './extensions/MyCustomNode'
const { editor } = useSlideEditor({
extensions: [
MyCustomNode.configure({
// custom options
})
]
})Extensions in the extensions array are appended after ExtensionKit, allowing your extensions to override or extend default behavior.
Controlling Re-instantiation
By default, the editor instance persists across re-renders. Use deps to control when the editor is destroyed and recreated:
// Never recreate (default)
const { editor } = useSlideEditor({
extensions: [MyExtension]
})
// Recreate when userId changes
const { editor } = useSlideEditor({
extensions: [MyExtension],
deps: [userId]
})Without deps, changing extensions or other options will update the existing editor instance without recreating it. This is faster but may not work for all configuration changes.
useEditor
Lower-level hook for creating an editor instance without ExtensionKit. Use this when building a custom editor from scratch.
import { useEditor } from '@blockslides/react'
import { Document, Paragraph, Text } from '@blockslides/extensions'
function Editor() {
const editor = useEditor({
extensions: [Document, Paragraph, Text],
content: '<p>Hello world</p>'
})
return <EditorContent editor={editor} />
}Configuration
const editor = useEditor({
// Required: Extensions array
extensions: [Document, Paragraph, Text, Bold, Italic],
// Initial content
content: '<p>Hello</p>',
// SSR configuration
immediatelyRender: false,
// Re-render on every transaction (legacy mode)
shouldRerenderOnTransaction: false,
// Lifecycle callbacks
onCreate: ({ editor }) => {
console.log('Created')
},
onUpdate: ({ editor, transaction }) => {
console.log('Updated')
},
// Editor configuration
editable: true,
editorProps: {
attributes: {
class: 'prose'
}
}
}, [/* deps */])Parameters:
All parameters from EditorOptions plus:
- immediatelyRender (
boolean) - Setfalsefor SSR compatibility. - shouldRerenderOnTransaction (
boolean) - Enable legacy re-render behavior. Defaults tofalse.
Returns:
Editor | null- Editor instance, ornullduring SSR.
useEditor vs useSlideEditor
| Feature | useEditor | useSlideEditor |
|---|---|---|
| ExtensionKit included | ❌ No | ✅ Yes |
| Add-slide button | ❌ Manual | ✅ Built-in |
| Preset templates | ❌ Manual | ✅ Built-in |
| Return value | Editor only | Editor + presets |
| Use case | Custom editors | Slide presentations |
Use useEditor when you need full control over the extension stack. Use useSlideEditor for slide presentation editors with sensible defaults.
useEditorState
Subscribe to specific parts of editor state and re-render only when those parts change. This hook is essential for building reactive UI components that respond to editor changes.
import { useEditorState } from '@blockslides/react'
function BoldButton({ editor }) {
const { isBold } = useEditorState({
editor,
selector: ({ editor }) => ({
isBold: editor.isActive('bold')
})
})
return (
<button
onClick={() => editor.chain().focus().toggleBold().run()}
data-active={isBold}
>
Bold
</button>
)
}Selector Function
The selector receives a snapshot containing the editor instance and transaction number:
const state = useEditorState({
editor,
selector: ({ editor, transactionNumber }) => {
return {
// Subscribe to specific state
selection: editor.state.selection,
canUndo: editor.can().undo(),
canRedo: editor.can().redo(),
wordCount: editor.state.doc.textContent.split(/\s+/).length
}
}
})The component only re-renders when the returned value changes, determined by deep equality comparison.
Custom Equality Function
Provide a custom equality function for fine-grained control:
const { selection } = useEditorState({
editor,
selector: ({ editor }) => ({
selection: editor.state.selection
}),
equalityFn: (a, b) => {
// Only re-render if selection position changed
return a.selection.from === b.selection.from &&
a.selection.to === b.selection.to
}
})Performance Optimization
Use useEditorState to prevent unnecessary re-renders when building toolbar components:
function Toolbar({ editor }) {
// Each button subscribes to only its own state
return (
<div>
<BoldButton editor={editor} />
<ItalicButton editor={editor} />
<UndoButton editor={editor} />
</div>
)
}
function BoldButton({ editor }) {
// Only re-renders when bold state changes
const isBold = useEditorState({
editor,
selector: ({ editor }) => editor.isActive('bold')
})
return (
<button onClick={() => editor.chain().focus().toggleBold().run()}>
{isBold ? 'Bold ✓' : 'Bold'}
</button>
)
}
function ItalicButton({ editor }) {
// Only re-renders when italic state changes
const isItalic = useEditorState({
editor,
selector: ({ editor }) => editor.isActive('italic')
})
return (
<button onClick={() => editor.chain().focus().toggleItalic().run()}>
{isItalic ? 'Italic ✓' : 'Italic'}
</button>
)
}Without useEditorState, all toolbar buttons would re-render on every editor transaction.
Transaction Number
Access the transaction number for debugging or tracking changes:
const { count } = useEditorState({
editor,
selector: ({ transactionNumber }) => ({
count: transactionNumber
})
})
console.log(`${count} transactions executed`)Multiple Selectors
Call useEditorState multiple times with different selectors for granular subscriptions:
function EditorStats({ editor }) {
const wordCount = useEditorState({
editor,
selector: ({ editor }) =>
editor.state.doc.textContent.split(/\s+/).filter(Boolean).length
})
const charCount = useEditorState({
editor,
selector: ({ editor }) =>
editor.state.doc.textContent.length
})
const slideCount = useEditorState({
editor,
selector: ({ editor }) => {
let count = 0
editor.state.doc.descendants(node => {
if (node.type.name === 'slide') count++
})
return count
}
})
return (
<div>
<span>{wordCount} words</span>
<span>{charCount} characters</span>
<span>{slideCount} slides</span>
</div>
)
}useCurrentEditor
Access the editor instance from context when using EditorProvider. This enables editor access deep in the component tree without prop drilling.
import { useCurrentEditor } from '@blockslides/react'
function Toolbar() {
const { editor } = useCurrentEditor()
if (!editor) return null
return (
<button onClick={() => editor.chain().focus().toggleBold().run()}>
Bold
</button>
)
}Returns:
- editor (
Editor | null) - Current editor instance from context, ornullif outside provider.
EditorProvider
useCurrentEditor must be used within an EditorProvider component. See UI Components for full documentation on EditorProvider.
useReactNodeView
Access React node view context when building custom node views. This hook provides callbacks for drag handling and content rendering.
import { useReactNodeView } from '@blockslides/react'
function MyCustomNodeView() {
const { onDragStart, nodeViewContentRef, nodeViewContentChildren } = useReactNodeView()
return (
<div draggable onDragStart={onDragStart}>
<div ref={nodeViewContentRef}>
{nodeViewContentChildren}
</div>
</div>
)
}Returns:
- onDragStart (
(event: DragEvent) => void) - Callback to handle drag start events - nodeViewContentRef (
(element: HTMLElement | null) => void) - Ref callback for the content container - nodeViewContentChildren (
ReactNode) - Children to render inside the node view content
This hook is used internally by NodeViewWrapper and NodeViewContent components. Most custom node views should use those components instead of calling this hook directly.