Using NodeViews with Tracked Changes
NodeViews allow you to render custom components for specific node types in your editor. When using NodeViews with Tracked Changes, you need to ensure that suggestion metadata is properly passed to your component's DOM elements.
What are NodeViews?
NodeViews are custom React, Vue, or vanilla JavaScript components that replace the default rendering of specific node types. They're commonly used for:
- Images and media
- Tables
- Custom blocks or cards
- Embeds and interactive content
- Any node type that requires custom rendering logic
Why NodeViews Need Special Handling
When Tracked Changes is enabled, the extension adds metadata to track modifications. For text content, this happens automatically through marks. However, for NodeViews, the suggestion metadata must be explicitly passed to your component.
Without proper handling, suggestion attributes won't reach your component's DOM, meaning:
- Suggestions won't be visually highlighted
- Suggestion queries may not find your NodeView changes
- The suggestion tracking will be incomplete
The Problem: Missing Suggestion Attributes
By default, when your NodeView receives HTMLAttributes, it contains suggestion metadata mixed with other attributes:
// HTMLAttributes prop received by your NodeView
{
'data-suggestion-id': 'suggestion-123',
'data-suggestion-type': 'delete',
'data-suggestion-user-id': 'user-456',
'data-suggestion-created-at': '2024-01-30T12:34:56Z',
// ... other non-suggestion attributes
}If you don't spread these attributes onto your actual DOM elements, the suggestion tracking system can't find them.
Incorrect (won't track suggestions):
function ImageNodeView({ node, HTMLAttributes }) {
return (
<img src={node.attrs.src} alt={node.attrs.alt} />
)
// Problem: HTMLAttributes not spread — suggestions lost
}The Solution: getSuggestionHTMLAttributes()
The getSuggestionHTMLAttributes() utility filters HTMLAttributes to extract only suggestion-related attributes (data-suggestion*). This lets you spread them safely onto your component:
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function ImageNodeView({ node, HTMLAttributes }) {
const suggestionAttrs = getSuggestionHTMLAttributes(HTMLAttributes)
return (
<img
src={node.attrs.src}
alt={node.attrs.alt}
{...suggestionAttrs}
/>
)
// Now: suggestion attributes are on the DOM element
}Implementation Examples
Simple Image NodeView
For a custom image component:
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function ImageNodeView({ node, HTMLAttributes }) {
const suggestionAttrs = getSuggestionHTMLAttributes(HTMLAttributes)
return (
<figure {...suggestionAttrs}>
<img
src={node.attrs.src}
alt={node.attrs.alt}
className="editor-image"
/>
{node.attrs.caption && <figcaption>{node.attrs.caption}</figcaption>}
</figure>
)
}Custom Block NodeView
For a more complex custom block:
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function CustomBlockNodeView({ node, HTMLAttributes }) {
const suggestionAttrs = getSuggestionHTMLAttributes(HTMLAttributes)
return (
<div
{...suggestionAttrs}
className="custom-block"
style={{
backgroundColor: node.attrs.bgColor,
padding: '16px',
}}
>
<h3>{node.attrs.title}</h3>
<p>{node.attrs.description}</p>
</div>
)
}Table Cell with NodeView
For table cells or similar nested structures:
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function TableCellNodeView({ node, HTMLAttributes }) {
const suggestionAttrs = getSuggestionHTMLAttributes(HTMLAttributes)
return (
<td {...suggestionAttrs}>
{node.content}
</td>
)
}Best Practices
Always filter suggestion attributes. Even if you only have suggestion attributes now, using getSuggestionHTMLAttributes() makes your code more maintainable and explicit about your intent.
Spread onto the root element. Place suggestion attributes on the outermost DOM element of your NodeView so the entire component is tracked as a unit.
Don't spread non-suggestion attributes to DOM. If your HTMLAttributes contain other metadata that shouldn't be on DOM elements (like internal React/Vue keys), use the utility to avoid pollution:
function MyNodeView({ node, HTMLAttributes }) {
const suggestionAttrs = getSuggestionHTMLAttributes(HTMLAttributes)
const { someInternalProp, ...restAttrs } = HTMLAttributes
return (
<div {...suggestionAttrs}>
{/* Handle other attributes selectively */}
</div>
)
}Test your implementation. After implementing a NodeView, verify that:
- The component renders correctly with and without suggestions
- Suggestions visually highlight (with your CSS)
- Accepting/rejecting suggestions works as expected
- Suggestion queries find your NodeView changes
Querying NodeView Suggestions
Once properly integrated, you can query suggestions on NodeViews just like text suggestions:
import { findSuggestions, getSuggestionAtSelection } from '@tiptap-pro/extension-tracked-changes'
// Find all suggestions (including those on NodeViews)
const all = findSuggestions(editor)
// Find suggestions at the current selection
const current = getSuggestionAtSelection(editor)
// Accept/reject suggestions on NodeViews
editor.commands.acceptSuggestion({ id: 'suggestion-123' })
editor.commands.rejectSuggestion({ id: 'suggestion-123' })