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
Tracked Changes stores suggestion metadata directly on the node's attributes (node.attrs). Your NodeView needs to map those attrs to data-suggestion-* DOM attributes so the tracking system can find them.
Incorrect (won't track suggestions):
function ImageNodeView({ node }) {
return (
<img src={node.attrs.src} alt={node.attrs.alt} />
)
// Problem: suggestion attrs from node.attrs not on the DOM — suggestions lost
}The Solution: getSuggestionHTMLAttributes()
The getSuggestionHTMLAttributes() utility reads suggestion data directly from the live ProseMirror node and returns the corresponding data-suggestion-* DOM attributes, safe to spread onto your component.
Pass node, not HTMLAttributes. The HTMLAttributes NodeView prop is computed once at NodeView creation and never updated, so it will be stale whenever suggestion state changes. The node prop always reflects the current state.
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function ImageNodeView({ node }) {
const suggestionAttrs = getSuggestionHTMLAttributes(node)
return (
<img
src={node.attrs.src}
alt={node.attrs.alt}
{...suggestionAttrs}
/>
)
}Implementation Examples
Simple Image NodeView
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function ImageNodeView({ node }) {
const suggestionAttrs = getSuggestionHTMLAttributes(node)
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
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function CustomBlockNodeView({ node }) {
const suggestionAttrs = getSuggestionHTMLAttributes(node)
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
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function TableCellNodeView({ node }) {
const suggestionAttrs = getSuggestionHTMLAttributes(node)
return (
<td {...suggestionAttrs}>
{node.content}
</td>
)
}Best Practices
Always use getSuggestionHTMLAttributes(node). Deriving the attributes from the node directly ensures they stay in sync as suggestion state changes — e.g. when a suggestion is accepted, rejected, or a remote update arrives via collaboration.
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.
Do not pass HTMLAttributes to the utility. The HTMLAttributes NodeView prop is a snapshot computed when the NodeView is first created. It is not updated on subsequent renders, so using it would produce stale data-suggestion-* attributes after any state change.
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' })