Invalid position when deleting/replacing content

EDIT: After some more sleuthing, I found it was a conflict between my manually dispatched action of replacing the text range from my containing React component, and the internally generated transaction of moving the cursor one forward after pressing the space bar. The suggestions plugin would modify the selection state in the first transaction, resulting in an different state by the time the second transaction would show up. I fixed it by swallowing the space bar event in the plugin and adding the space in the manually created transaction, thereby collapsing the two transactions into one under my control, but is this the right way to handle something like this?

Forgive the ignorant question; I’m sure I’m doing something wrong managing the state of my editor, but I haven’t been able to figure it out. I started using PM today and I am implementing a basic mentions feature. I would like to detect a piece of text matching a pattern, make an API call based on that text, return the results, replace the typed text with a custom node, and then use a Node View to render a React component in the node’s place.

I have accomplished all of this except for actually replacing the text with a node. I’m using a plugin for the text matching hooks, found here: I’m finding that when I apply the transaction that replaces the range of the mention with a node, I get an Invalid Position Error upon updating the editor state. For a split second, I do see that my react component is rendered successfully, followed by a crash. After some experimentation, doing any kind of replacing or deleting from the hooks of the plugin, that affect any of the decorated text of the plugin, cause this crash. Is there something special I need to do with my selection state to avoid this?

If it helps, I am using the pattern for React integration found in this post: Using with React

function insertSmartLink(range, type, id, smartlink) {
  return function(state, dispatch) {
    if (dispatch) {
      let tr =
      tr.replaceWith(range.from,, smartlink.create({type, id}))
    return true

const plugins = (schema, menu, onSearchChange, onSearchExit) => {
  return [
      debug: false,
      matcher: triggerCharacter('@', { allowSpaces: false }),
      onEnter(args) {
        onSearchChange({ value: args.text, range: args.range });
      onChange(args) {
        onSearchChange({ value: args.text, range: args.range });
      onExit(args) {
      onKeyDown(args) {
        // console.log(event.key);
        return false;
    ...exampleSetup({schema: schema, menuContent: menu.fullMenu })

1 Like