Getting "RangeError: Applying a mismatched transaction" even with trivial code

I’m getting this error RangeError: Applying a mismatched transaction — unfortunately I don’t have a clean repro yet, but I do have something minimal: even simply loading the page (or not), typing some text, and calling

view.dispatch(view.state.tr)

throws this error. It is hard to understand how this could happen, because presumably the transaction has just now been created out of the current view.state, so it should not be mismatched.

However, if I call

view.updateState(view.state.apply(view.state.tr))

that works fine, and once I’ve run that, I can run

view.dispatch(view.state.tr)

with no issue.

Any idea what could be causing something like this?

Did you set a custom dispatch function? The default one just makes precisely the updateState call you show, so it seems like that should not behave differently.

Not using a custom dispatch function; in fact my schema and the editor setup is pretty minimal — this is as far as I got in minimizing the example:

At this point I’m beginning to suspect that the issue is not exactly within ProseMirror but some unfortunate interaction with Alpine.js (https://alpinejs.dev/): maybe Alpine’s “observability” or “reactivity” or whatever of this.view is causing issues somehow.

I haven’t isolated the issue yet, any help appreciated!

I guess that’s indeed the issue: there’s a similar discussion at AlpineJS v3 - Range Error: Applying a mismatched transaction · Issue #1515 · ueberdosis/tiptap · GitHub (haven’t read it closely enough yet to understand how to avoid the issue).

Ok, I finally got time to read the link, and I think I understand somewhat.

Alpine plays some tricks with getters and setters for reactivity (it uses Vue’s reactivity system) so that when we say this.view = new EditorView(...), it actually sets this.view to a Proxy rather than the EditorView itself. Later, when we access this.view (or even if we say window.view = this.view and access window.view directly), it so happens that between two references to view in view.dispatch(view.state.tr), they are… mismatched, somehow, because something has changed. (I guess I don’t really understand the actual problem.) Also see this comment on the Tiptap issue.

There are (at least) two solutions:

  • Avoid the pitfalls from going via Alpine, just bind the EditorView to (say) window.view and use it directly.

  • Use a closure:

    function createEditor() {
      let view = new EditorView(/*...*/);
      return () => {
        return view;
      }
    

    so that even after this.view = createEditor(), the actual “raw” EditorView is still available as long as you refer to this.view() everywhere instead of to this.view (which is now a proxy to the actual function I guess, but that doesn’t matter).

I don’t know if there’s another kind of solution where Alpine can be told not to do make view reactive (like Vue’s shallowRef), which would be really nice (wouldn’t have to say this.view() instead of this.view), but for my purposes the issue is solved. Sorry for the noise as this issue is not really ProseMirror-specific (I don’t know whether there’s anything ProseMirror could do differently here); I didn’t realize initially that this was happening because of Alpine.

2 Likes

Tank you. I was getting the same error in my vue app. You can actually bypass the proxy by using toRaw imported from vue. I do something like:

import { toggleMark } from 'prosemirror-commands';
import { schema } from 'prosemirror-schema-basic';
import { EditorView } from 'prosemirror-view';
import { ref, toRaw } from 'vue';

const editorView = ref(new EditorView({ /* ... */ }));
const command = toggleMark(schema.marks.strong);

// ...

const view = toRaw(editorView.value);
command(view.state, view.dispatch);
1 Like