Detect Changes in WordPress Gutenberg, Programmatically

Jan 03, 2024
 — by 
Kris Kratz
 in 

Recently when I resumed work on my WordPress meta box plugins for RankNavigator, I found that the it was no longer updating the score when after I included target terms.

Well, the bottom line is the way I monitored changes to the Gutenberg Editor were deprecated. That’s right, getUndoEdit and getRedoEdit are now rendered useless for my use in the Block Editor. So how is the plugin to know when content changes occur?

The reason I like those two is that they can detect any and all changes to the editor, including typing, clipboard actions (like copy and paste), and history changes (like undo and redo).

Temporarily I switched it back to the old isTyping detection, but it is literally only for typing. It doesn’t detect clipboard actions or history level changes.

Inside the sidebar module, I add a useEffect hook to subscribe to is typing like this:

wp.data.subscribe(() => {
	const isTyping = wp.data.select('core/block-editor').isTyping()
	if(isTyping) setTypingTimeout()
})

This little bit of WordPress React magic detects changes to the Block Editor’s isTyping. If it returns true, then the function fires the setTypingTimeout function, which delays the function running and also debounces. We only want it to run when the users has stopped typing for a little bit of time. Otherwise, the scoring script would run nearly constantly, and it would dramatically slow down the user interface.

This is also inside of the same useEffect hook as the subscribe above. I just use a regular variable here.

let timeoutId = 0;

const setTypingTimeout = () => {
	window.clearTimeout( timeoutId );
	timeoutId = setTimeout( () => {
		myFunction();
	}, 1000 );
};

How to tackle the other two? We want to detect changes on copy, paste, undo, and redo. If I can only access the history changes directly… So we need a more complete solution since the WordPress team got rid of getUndoEdits and getRedoEdits in WordPress 6.3.

This little function will do the trick:

let contentState = wp.data.select( 'core/editor' ).getEditedPostContent()
wp.data.subscribe( _.debounce( ()=> {
	let newContentState = wp.data.select( 'core/editor' ).getEditedPostContent()
	if ( contentState !== newContentState ) {
		console.log( 'triggered' )
		setTypingTimeout() // Reset timeout for function
	}
	// Update reference to content
	contentState = newContentState;
}, 500 ) )

The key is tracking the content of the post using subscribe and delaying the comparisons with debounce.