Exploring the Interactivity API
WordCamp Buffalo 2024
colorfultones.com/presentation/exploring-the-interactivity-api/
Presented by Damon Cook
Interactivity API = IAPI
Milestones…
January 11, 2023
Update on the work to make building interactive blocks easier
August 15, 2023
February 19, 2024
March 4, 2024
What do we mean by interactivity?
Examples of IAPI in WordPress
What do we need an
Interactivity API for?
Why not use Alpine, React, or Vue?
- Block-first and PHP-first: Utilizes block building and server-side rendering to enhance SEO and performance, optimizing both user and developer experiences.
- Backward-compatible: Supports both classic and block themes, with optional compatibility with other JavaScript frameworks. It integrates with hooks and internationalization, although using the API as the primary method is recommended.
- Declarative and reactive: Employs declarative coding to define interactions and reacts to data changes by selectively updating the DOM.
- Performant: Enhances runtime efficiency to ensure a fast and lightweight user experience.
- Send less JavaScript: Minimizes the JavaScript payload by using a shared framework that blocks can reuse, reducing the total JavaScript sent as more blocks adopt the Interactivity API.
Where do I start?
Block Editor Handbook / Reference Guides / Interactivity API Reference / Quick start guide
Requirements
Getting started from scratch…
@wordpress/create-block
Interactivity API template for scaffolding
🔴 🟡 🟢
npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template
🔴 🟡 🟢
cd my-first-interactive-block && npm start
Block Editor Handbook -> Interactivity API -> Quick Start Guide
Getting started with integrating…
Add interactivity
support in block.json
🔴 🟡 🟢
block.json
"supports": {
"interactivity": true
},
"viewScriptModule": "file:./view.js"
Add wp-interactive
directive to a DOM element
🔴 🟡 🟢
render.php
<div data-wp-interactive="myPlugin">
<!-- Interactivity API zone -->
</div>
Block Editor Handbook -> Interactivity API
IAPI Reference
Let’s take a peek at the underlying
pieces that make up the IAPI.
What are directives?
Directives: custom attributes that are added to the markup of your block to add behavior to its DOM elements. Interactivity API directives use the data-wp-
prefix.
🔴 🟡 🟢
render.php
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "isOpen": false }'
data-wp-watch="callbacks.logIsOpen"
>
<button
data-wp-on--click="actions.toggle"
data-wp-bind--aria-expanded="context.isOpen"
aria-controls="p-1"
>
Toggle
</button>
<p id="p-1" data-wp-bind--hidden="!context.isOpen">
This element is now visible!
</p>
</div>
Directives can also be injected dynamically using the HTML Tag Processor.
List of directives
wp-interactive
*
“Activates” the interactivity for the DOM element and its children through the Interactivity API (directives and store).
Namespace string example
🔴 🟡 🟢
render.php
<!-- Let's make this element and its children interactive and set the namespace -->
<div
data-wp-interactive="myPlugin"
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
Namespace object example
🔴 🟡 🟢
render.php
<!-- Let's make this element and its children interactive and set the namespace -->
<div
data-wp-interactive='{ "namespace": "myPlugin" }'
data-wp-context='{ "myColor" : "red", "myBgColor": "yellow" }'
>
<p>I'm interactive now, <span data-wp-style--background-color="context.myBgColor">and I can use directives!</span></p>
<div>
<p>I'm also interactive, <span data-wp-style--color="context.myColor">and I can also use directives!</span></p>
</div>
</div>
wp-context
*
It provides a local state available to a specific HTML node and its children.
The wp-context
directive accepts a stringified JSON as a value.
Context directive in render.php
🔴 🟡 🟢
render.php
<div data-wp-context='{ "post": { "id": <?php echo $post->ID; ?> } }'>
<button data-wp-on--click="actions.logId">
Click Me!
</button>
</div>
See store used with the directive above
🔴 🟡 🟢
view.js
store( "myPlugin", {
actions: {
logId: () => {
const { post } = getContext();
console.log( post.id );
},
},
} );
wp-context
* – nesting
Different contexts can be defined at different levels, and deeper levels will merge their context with any parent one:
🔴 🟡 🟢
render.php
<div data-wp-context="{ foo: 'bar' }">
<span data-wp-text="context.foo"><!-- Will output: "bar" --></span>
<div data-wp-context="{ bar: 'baz' }">
<span data-wp-text="context.foo"><!-- Will output: "bar" --></span>
<div data-wp-context="{ foo: 'bob' }">
<span data-wp-text="context.foo"><!-- Will output: "bob" --></span>
</div>
</div>
</div>
The store
The store is used to create the logic (actions, side effects, etc.) linked to the directives and the data used inside that logic (state, derived state, etc.).
The store is usually created in the view.js
file of each block, although the state can be initialized from the render.php
of the block.
Elements of the store
- State
- Global state
- Context / local state
- Actions
- Side effects
- Derived state
State
It defines data available to the HTML nodes of the page. It is important to differentiate between two ways to define the data:
- Global state: It is defined using the
store()
function with thestate
property, and the data is available to all the HTML nodes of the page. - Context/Local State: It is defined using the
data-wp-context
directive in an HTML node, and the data is available to that HTML node and its children. It can be accessed using thegetContext
function inside of an action, derived state or side effect.
render.php
🔴 🟡 🟢
render.php
<div data-wp-context='{ "someText": "Hello World!" }'>
<!-- Access global state -->
<span data-wp-text="state.someText"></span>
<!-- Access local state (context) -->
<span data-wp-text="context.someText"></span>
</div>
view.js
🔴 🟡 🟢
view.js
const { state } = store( "myPlugin", {
state: {
someText: "Hello Universe!"
},
actions: {
someAction: () => {
state.someText // Access or modify global state - "Hello Universe!"
const context = getContext();
context.someText // Access or modify local state (context) - "Hello World!"
},
},
} )
Actions
Actions are just regular JavaScript functions. Usually triggered by the data-wp-on
directive (using event listeners) or other actions.
Async actions should use generators instead of async/await.
render.php
🔴 🟡 🟢
render.php
<div data-wp-context='{ "someText": "Hello World!" }'>
<!-- Access global state -->
<span data-wp-text="state.someText"></span>
<!-- Access local state (context) -->
<span data-wp-text="context.someText"></span>
</div>
view.js
🔴 🟡 🟢
view.js
const { state } = store( "myPlugin", {
state: {
someText: "Hello Universe!"
},
actions: {
someAction: () => {
state.someText // Access or modify global state - "Hello Universe!"
const context = getContext();
context.someText // Access or modify local state (context) - "Hello World!"
},
},
} )
Side effects
Automatically react to state changes. Usually triggered by data-wp-watch
or data-wp-init
directives.
Derived state
They return a computed version of the state. They can access both state
and context
.
Example of derived state
🔴 🟡 🟢
view.js
const { state } = store( "myPlugin", {
state: {
amount: 34,
defaultCurrency: 'EUR',
currencyExchange: {
USD: 1.1,
GBP: 0.85,
},
get amountInUSD() {
return state.currencyExchange[ 'USD' ] * state.amount,
},
get amountInGBP() {
return state.currencyExchange[ 'GBP' ] * state.amount,
},
},
} );
Accessing data
The store
contains all the store properties, like state
, actions
or callbacks
. They are returned by the store()
call, so you can access them by destructuring it:
🔴 🟡 🟢
view.js
const { state, actions } = store( "myPlugin", {
// ...
} );
The store()
function can be called multiple times and all the store parts will be merged:
Example of multiple store calls
🔴 🟡 🟢
view.js
store( "myPlugin", {
state: {
someValue: 1,
}
} );
const { state } = store( "myPlugin", {
actions: {
someAction() {
state.someValue // = 1
}
}
} );
Setting the store – client side
The store
method used to set the store in JavaScript can be imported from @wordpress/interactivity
.
🔴 🟡 🟢
view.js
import { store, getContext } from '@wordpress/interactivity';
store( "myPlugin", {
actions: {
toggle: () => {
const context = getContext();
context.isOpen = !context.isOpen;
},
},
callbacks: {
logIsOpen: () => {
const { isOpen } = getContext();
// Log the value of `isOpen` each time it changes.
console.log( `Is open: ${ isOpen }` );
}
},
});
Setting the store – server side
The state can also be initialized on the server using the wp_interactivity_state()
function.
🔴 🟡 🟢
render.php
wp_interactivity_state( 'favoriteMovies', array(
"1" => array(
"id" => "123-abc",
"movieName" => __( "someMovieName", "textdomain" )
),
) );
Store client methods
The store has some handy methods to allow developers to access data.
getContext()
getElement()
getContext()
method
Retrieves the context inherited by the element evaluating a function from the store. The returned value depends on the element and the namespace where the function calling getContext()
exists.
🔴 🟡 🟢
render.php
<div data-wp-interactive="myPlugin" data-wp-context='{ "isOpen": false }'>
<button data-wp-on--click="actions.log">Log</button>
</div>
view.js
🔴 🟡 🟢
view.js
import { store, getContext } from '@wordpress/interactivity';
store( "myPlugin", {
actions: {
log: () => {
const context = getContext();
// Logs "false"
console.log( 'context => ', context.isOpen )
},
},
});
getElement()
method
Retrieves a read-only representation of the element that the action is bound to or called from. It returns an object with two keys:
ref
is the reference to the DOM element as an HTMLElement.
attributes
contains a Proxy, which adds a getter that allows to reference other store namespaces.
🔴 🟡 🟢
view.js
console.log( 'element attributes => ', element.attributes );
// Gives us...
{
"data-wp-on--click": 'actions.increaseCounter',
"children": ['Log'],
"onclick": event => { evaluate(entry, event); }
}
Server functions
wp_interactivity_config
– allows to set or get a configuration array, referenced to a store namespace.wp_interactivity_process_directives
– returns the updated HTML after the directives have been processed.- The core function of the Interactivity API server-side rendering. Any HTML can be processed, whether is a block or not.
wp_interactivity_data_wp_context
– returns a stringified JSON of a context directive.
Dive deeper and keep learning
A first look at the Interactivity API – create a donation calculator.
Existing examples
Gallery Slider – Ryan Welcher
WordPress core blocks: Image lightbox, Search show/hide, Query Loop enhanced pagination
Pew Research Center’s PRC Block Library