Issue with plugin state receiving stale value

I’m cooking up a simple placeholder plugin, and I’m seeing a really weird issue with receiving a stale value in the state apply(tr, value) function.

I’m setting up plugin state with init {:text "foo bar"}. I’m dispatching an empty transaction to change the state. So far this works. But at the moment I type in the editor (dispatching a new transaction), the value argument of the apply function is still the original state {text: "foo bar"} so then by returning it, it overwrites the correct text.

My entire plugin looks like this:

import {PluginKey} from "prosemirror-state"

export const textPlaceholderPluginKey = new PluginKey("nosco-placeholder-key");

export function textPlaceholderPluginSpec() {
    return {
        key: textPlaceholderPluginKey,
    state: {
        init() { return {text: "foo bar"}; },
        apply(tr, value) {
            let meta = tr.getMeta(this);
            console.log("NEW meta", meta)
            console.log("old value", value)
            if (meta) {
                return {text: meta.text};
            } else {
                return value;
        view() {
            return {
                update: (view) => {
                    let doc = view.state.doc;
                    let text = textPlaceholderPluginKey.getState(view.state);
                    let isDocEmpty =
                        doc.childCount === 1 &&
                        doc.firstChild.isTextblock &&
                        doc.firstChild.content.size === 0;

                    console.log("VIEW TEXT placehodler", text.text);
                    if (!isDocEmpty) {
                    } else {
                        view.dom.setAttribute("data-placeholder", text.text);


and the console is:

 NEW meta undefined
 old value {text: 'foo bar'}
 VIEW TEXT placehodler foo bar
 NEW meta {text: 'MY PLACEHOLDER'}
 old value {text: 'foo bar'}
<type a single key here>
 NEW meta undefined
 old value {text: 'foo bar'}
 VIEW TEXT placehodler foo bar

This looks like either a significant bug or I’m missing something fundamental here.

Not sure what you’re doing, since the VIEW TEXT output from the plugin should definitely not occur before the state field update output. What does the code that dispatches the transaction with metadata look like?

It’s possible that this is caused by your mutations of view.dom triggering an update via the DOM observer. Have you tried using the attributes prop instead?

Sorry for the false alarm, and thanks for pointing out the attributes editor prop. It turns out this was a race condition in React that was resetting the editor state to some other value.