How to update plugin state from handleKeydown props

So I’m working on a plugin that does some basic autocomplete stuff and I want to hijack the arrow keys to navigate the autocomplete menu. However, I’m struggling to figure out how to mutate the plugin’s state from the handleKeyDown callback props.

const tagPlugin = new Plugin({
	key: pluginKey,
	state: {
		init() {
			return {index: 0, suggestions: ["a", "b"]}
		},
		apply(tr, state) {
			return state
		},
	},
	props: {
		handleKeyDown(view, e) {
			var state = this.getState(view.state)

			const down = e.keyCode === 40
			if (down) {
				const newState = {
					...state,
					index: Math.min(state.index + 1, state.suggestions.length - 1),
				}
				console.log("DOWN", newState.index)

				// How do I mutate the plugin state from here?
				// view.dispatch(view.state.tr.setMeta(pluginKey, newState))
			}
		}
	}
})

Any ideas?

Thanks

The typical way to do this is to have your plugin state’s apply method check the transaction for some specific metadata property, and update based on that. Your key handler would then dispatch a transaction with that metadata property.

I see, thanks for the help!

Just for posterity, this is what I was able to get working :grinning_face_with_smiling_eyes:

const tagPlugin = new Plugin({
	key: pluginKey,
	state: {
		init() {
			return {index: 0, suggestions: ["a", "b"]}
		},
		apply(tr, state) {
			const newPluginState = tr.getMeta(pluginKey)
			if (newPluginState) {
				return newPluginState
			}

			return state
		},
	},
	props: {
		handleKeyDown(view, e) {
			var state = this.getState(view.state)

			const down = e.keyCode === 40
			if (down) {
				const newState = {
					...state,
					index: Math.min(state.index + 1, state.suggestions.length - 1),
				}
				view.dispatch(view.state.tr.setMeta(pluginKey, newState))
			}
		}
	}
})

Some food-for-thought: maybe I’m just new to ProseMirror, but I think there might be a way to clean up the API in a way that feels more intuitive. I think it would be slightly less confusing if some of these relationships were more consistent somehow.

(1) since we’re using this.getState, it would be convenient if we used this.setState as well, though that probably makes the internal architecture a little more ugly.

(2) since we use tr.setMeta and tr.getMeta, maybe we can mimic this functionality for getting the plugin state – something like view.state.getMeta(tagPlugin) would help me connect the dots a little better.

Thanks for the help and the awesome library!

1 Like

it would be convenient if we used this.setState as well

That would be an imperative update function, which is entirely incompatible with the idea of transaction objects and immutable states.