WordPress 7.0 is close, and the parts of it worth reading are not the UI ones. They are the changes to the Interactivity API. I have lost enough hours to race conditions in custom Gutenberg blocks to know that when Core touches state management, it is worth reading twice.
This round is about programmatic control: a new side-effect primitive, plus some overdue sanity in the router store. If you have ever tried to sync state across two stores, or wire up analytics without hanging directives off half your markup, these are the changes to look at.
The new watch() function for side effects
Reacting to a state change in the Interactivity API used to mean the data-wp-watch directive on a specific DOM element. That works for a simple UI toggle, but it is painful for anything higher up: logging, keeping two independent stores in sync, or any side effect that does not belong to one HTML tag.
WordPress 7.0 adds a watch() function to the @wordpress/interactivity package, so you can subscribe to reactive values from JavaScript. It runs once on initialization, then again every time a dependency used inside the callback changes.
import { store, watch } from '@wordpress/interactivity';
const { state } = store( 'bbioon/myPlugin', {
state: {
counter: 0,
},
} );
// This runs immediately and whenever state.counter updates.
const unwatch = watch( () => {
console.log( `The current count is: ${ state.counter }` );
} );
// If you need to stop watching:
unwatch();
Watch your cleanup. Like React’s useEffect, the callback you pass to watch() can return a function that tears down listeners or timers before the next run. Skip it and you get memory leaks, which are miserable to track down in production.
Router store: state.url now comes from the server
Initialization in the core/router store has always been awkward. state.url was set on the client, so the value stayed undefined until the JS module finished loading asynchronously, and we all ended up writing if ( ! state.url ) return; guards so our logic did not fire too early.
From WordPress 7.0 the server populates state.url during directive processing. That makes it reliable from the first render, so you can pair watch() with state.url to record virtual page views without the initial undefined value counting as a navigation.
import { store, watch } from '@wordpress/interactivity';
const { state } = store( 'core/router' );
watch( () => {
// Reliably track every client-side navigation.
bbioon_send_analytics( state.url );
} );
Two internal properties go deprecated at the same time: state.navigation.hasStarted and state.navigation.hasFinished. They were only ever implementation details for the loading bar, and a sturdier public API for tracking navigation state is expected in WordPress 7.1.
I go through what this means for existing projects in my write-up on Interactivity API refinements, and the WordPress 7.0 dev notes have the official write-up.
If Interactivity API work is eating up your dev hours, I can take it on. I have been wrestling with WordPress since the 4.x days.
What to change in your code
Between watch() and server-side router state, the API is drifting away from a pile of DOM hacks and toward something that behaves like a real reactive framework. The practical move is to refactor your navigation tracking now, before the console warnings arrive in 7.1.