Interactivity API best practices in 6.8


WordPress 6.8 comes with a few new best practices and requirements in the Interactivity API that are part of a longer-term continuous-improvement effort. Some of the relevant changes in 6.8 are an intermediary step: They do not include these enhancements themselves, but they prepare the project to add them in a future release by adding two new deprecation warnings.
If you have been using the Interactivity API in your project, especially if you have been writing your own stores, please read on to learn how you can prepare your changes for the latest and future behavior of the API.
class=”wp-block-heading”>How to apply the latest best practices (and avoid deprecation warnings)
To help the Interactivity API speed up WordPress, the project is working towards running most store actions asynchronously by default, as a better foundation for achieving good INP (“Interaction to Next Paint”) performance. Right now, browsers invoke all synchronous Interactivity API event handlers as part of the same task—this means they stack up. This can make the user wait for longer than 50 milliseconds (also called a “long task”) for the site to react to some interaction, like clicking a button.
Starting with 6.8, and going forward, the Interactivity API’s push towards asynchronous handlers as the default will make those long tasks less likely. The 6.8 release only prepares for the transition. In the following WordPress release, the API will automatically yield to the main thread in between handlers, so ideally there’s nothing to stack up, and nothing to make the user wait. (Also refer to async actions and the splitTask()
function.)
This performance enhancement also helps with cross-plugin compatibility, as handlers for the same event may come from different plugins. The new requirements outlined below are an important step to prepare the Interactivity API for that future.
Wrap certain action callbacks in withSyncEvent()
Pay attention to any store action that is attached to an event listener (like data-wp-on--click
) and accesses the event
object: If the action callback uses any of the event
properties or methods below, you need to wrap it in a newly added utility function called withSyncEvent()
:
- Property:
event.currentTarget
- Method:
event.preventDefault()
- Method:
event.stopImmediatePropagation()
- Method:
event.stopPropagation()
Starting in WordPress 6.8, if any action callback uses the above event
properties or methods and is not wrapped in withSyncEvent()
, that action callback will trigger a deprecation warning. For now, the logic will continue to work as before. But in a future WordPress release it will break if you do not migrate. For example, event.preventDefault()
will not prevent the default action since the action will be asynchronous by default. As such, please make sure to resolve any deprecation warnings you see.
This correct (withSyncEvent()
:
import { store, withSyncEvent } from '@wordpress/interactivity';
store( 'myPlugin', {
actions: {
// `event.preventDefault()` requires synchronous event access.
preventNavigation: withSyncEvent( ( event ) => {
event.preventDefault();
} ),
// `event.target` does not require synchronous event access.
logTarget: ( event ) => {
console.log( 'event target => ', event.target );
},
// Not using `event` at all does not require synchronous event access.
logSomething: () => {
console.log( 'something' );
},
},
} );
This bad (
import { store } from '@wordpress/interactivity';
store( 'myPlugin', {
actions: {
// Missing `withSyncEvent()` around synchronous event access.
preventNavigation: ( event ) => {
event.preventDefault();
},
},
} );
Do not use actions to determine HTML attribute values
If you have been relying on Interactivity API store functions (like actions
or callbacks
) to determine HTML attribute values (e.g. via data-wp-bind--attr
), please revise these attributes now. Instead, use global state, local context, or derived state. And please do not combine these function calls with directive logic like the !
operator.
Starting in WordPress 6.8, any directive using a store function in combination with the !
operator will emit a deprecation warning. The logic will continue to work as before for now, but in a future WordPress release it will break if you do not migrate. More broadly, if you are using store functions in directives that determine HTML attribute values, please migrate to using global state, local context, or derived state instead. More deprecation warnings around incorrect usage of store functions are expected soon, and eventually unmigrated code is going to break.
Please refer to the following correct (
import { store } from '@wordpress/interactivity';
store( 'myPlugin', {
state: {
get isOpen() {
const ctx = getContext();
return !! ctx.open;
},
},
} );
Content.
This bad (
import { store } from '@wordpress/interactivity';
store( 'myPlugin', {
actions: {
isOpen() {
const ctx = getContext();
return !! ctx.open;
},
},
} );
Content.
To provide context on why this new requirement is relevant: Using store functions for anything other than the “on”, “init”, or “watch” groups of directives has always been an anti-pattern. It is now being more formally discouraged, and will in the future be made impossible.
Support for the .length
property in directives
An additional Interactivity API enhancement in WordPress 6.8 is support for the .length
property on strings and numeric arrays in directives, ensuring consistency between server and client rendering.
Previously, the .length
property was unavailable on the server, requiring workarounds. This update allows developers to use .length
within all directives that reference global state, local context, or derived state, aligning behavior across environments.
This code example illustrates using the .length
property:
This improvement streamlines logic and improves developer experience.
Summary and further reading
Please refer to the following links for further reading:
- Gutenberg pull request #68097 for the
withSyncEvent
and new directive requirement enhancements - Gutenberg issues #64944 and #69552 with additional context on the long-term plans to run Interactivity API actions asynchronously by default.
- Gutenberg issue #69269 with additional context on the long-term plans to more clearly separate directives that do something vs that determine a value.
- Trac ticket #62582 for support of the
.length
property. - Documentation for understanding global state, local context, or derived state.
Co-authored by @gziolo.
Props to @westonruter, @jonsurrell, @webcommsat, @marybaum for review and proofreading.