Using NodeViews with Tracked Changes

Paid add-on

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' })

See Also