Exploring the Interactivity API

WordCamp Buffalo 2024

colorfultones.com/presentation/exploring-the-interactivity-api/

Interactivity API = IAPI

What do we mean by interactivity?

Examples of IAPI in WordPress

Query Loop block – disable full-page reload
Image block – lightbox
Check out the WPMovies.dev demo and wp-movies-demo GitHub repository.

What do we need an
Interactivity API for?

Why not use Alpine, React, or Vue?

  1. Block-first and PHP-first: Utilizes block building and server-side rendering to enhance SEO and performance, optimizing both user and developer experiences.
  2. 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.
  3. Declarative and reactive: Employs declarative coding to define interactions and reacts to data changes by selectively updating the DOM.
  4. Performant: Enhances runtime efficiency to ensure a fast and lightweight user experience.
  5. 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.

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.

wp-interactive*

“Activates” the interactivity for the DOM element and its children through the Interactivity API (directives and store).

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.

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 the state 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 the getContext function inside of an action, derived state or side effect.

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.

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.

Accessing data

The store contains all the store properties, like stateactions 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:

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>

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

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