diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md
index 25498d10bde2ef..bbbb565684c578 100644
--- a/docs/reference-guides/interactivity-api/api-reference.md
+++ b/docs/reference-guides/interactivity-api/api-reference.md
@@ -6,8 +6,8 @@ Interactivity API is only available for WordPress 6.5 and above.
To add interactions to blocks using the Interactivity API, developers can use:
-- **Directives:** Added to the markup to add specific behavior to the DOM elements of the block
-- **Store:** Contains the logic and data (state, actions, side effects, etc.) needed for the behavior
+- **Directives:** Added to the markup to add specific behavior to the DOM elements of the block
+- **Store:** Contains the logic and data (state, actions, side effects, etc.) needed for the behavior
DOM elements are connected to data stored in the state and context through directives. If data in the state or context change directives will react to those changes, updating the DOM accordingly (see [diagram](https://excalidraw.com/#json=T4meh6lltJh6TCX51NTIu,DmIhxYSGFTL_ywZFbsmuSw)).
@@ -21,21 +21,21 @@ Interactivity API directives use the `data-` prefix. Here's an example of direct
```html
-
-
-
- This element is now visible!
-
+
+
+
+ This element is now visible!
+
```
@@ -52,24 +52,44 @@ The `wp-interactive` directive "activates" the interactivity for the DOM element
```html
-
I'm interactive now, and I can use directives!
-
-
I'm also interactive, and I can also use directives!
-
+
+ I'm interactive now,
+ and I can use directives!
+
+
+
+ I'm also interactive,
+ and I can also use directives!
+
+
-
I'm interactive now, and I can use directives!
-
-
I'm also interactive, and I can also use directives!
-
+
+ I'm interactive now,
+ and I can use directives!
+
+
+
+ I'm also interactive,
+ and I can also use directives!
+
+
```
@@ -96,31 +116,31 @@ The `wp-context` directive accepts a stringified JSON as a value.
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- logId: () => {
- const { post } = getContext();
- console.log( post.id );
- },
- },
+store( 'myPlugin', {
+ actions: {
+ logId: () => {
+ const { post } = getContext();
+ console.log( post.id );
+ },
+ },
} );
```
+
Different contexts can be defined at different levels, and deeper levels will merge their own context with any parent one:
```html
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
```
@@ -130,18 +150,18 @@ This directive allows setting HTML attributes on elements based on a boolean or
```html
-
-
- Title
-
- SUBMENU ITEMS
-
-
+
+
+ Title
+
+ SUBMENU ITEMS
+
+
```
@@ -149,33 +169,34 @@ This directive allows setting HTML attributes on elements based on a boolean or
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- toggleMenu: () => {
- const context = getContext();
- context.isMenuOpen = !context.isMenuOpen;
- },
- },
+store( 'myPlugin', {
+ actions: {
+ toggleMenu: () => {
+ const context = getContext();
+ context.isMenuOpen = ! context.isMenuOpen;
+ },
+ },
} );
```
+
The `wp-bind` directive is executed:
-- When the element is created
-- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
+- When the element is created
+- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
When `wp-bind` directive references a callback to get its final value:
-- The `wp-bind` directive will be executed each time there's a change on any of the properties of the `state` or `context` used inside this callback.
-- The returned value in the callback function is used to change the value of the associated attribute.
+- The `wp-bind` directive will be executed each time there's a change on any of the properties of the `state` or `context` used inside this callback.
+- The returned value in the callback function is used to change the value of the associated attribute.
The `wp-bind` will do different things when the DOM element is applied, depending on its value:
- - If the value is `true`, the attribute is added: `
`
- - If the value is `false`, the attribute is removed: `
`
- - If the value is a string, the attribute is added with its value assigned: `
`
+- If the value is `true`, the attribute is added: `
`
+- If the value is `false`, the attribute is removed: `
`
+- If the value is a string, the attribute is added with its value assigned: `
`
### `wp-class`
@@ -183,20 +204,20 @@ This directive adds or removes a class to an HTML element, depending on a boolea
```html
-
- Option 1
-
-
- Option 2
-
+
+ Option 1
+
+
+ Option 2
+
```
@@ -204,21 +225,22 @@ This directive adds or removes a class to an HTML element, depending on a boolea
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- toggleSelection: () => {
- const context = getContext();
- context.isSelected = !context.isSelected
- }
- }
+store( 'myPlugin', {
+ actions: {
+ toggleSelection: () => {
+ const context = getContext();
+ context.isSelected = ! context.isSelected;
+ },
+ },
} );
```
+
The `wp-class` directive is executed:
-- When the element is created
-- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
+- When the element is created
+- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
The boolean value received by the directive is used to toggle (add when `true` or remove when `false`) the associated class name from the `class` attribute.
@@ -229,24 +251,24 @@ So, for example, use the class name `is-dark` instead of `isDark` and `data-wp-c
```html
-
+
-
+
```
```css
/* Recommended */
.is-dark {
- /* ... */
+ /* ... */
}
/* Not recommended */
.isDark {
- /* ... */
+ /* ... */
}
```
@@ -255,9 +277,11 @@ So, for example, use the class name `is-dark` instead of `isDark` and `data-wp-c
This directive adds or removes inline style to an HTML element, depending on its value. It follows the syntax `data-wp-style--css-property`.
```html
-
-
-
Hello World!
+
+
+
Hello World!
>
```
@@ -266,26 +290,27 @@ This directive adds or removes inline style to an HTML element, depending on its
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- toggleContextColor: () => {
- const context = getContext();
- context.color = context.color === 'red' ? 'blue' : 'red';
- },
- },
+store( 'myPlugin', {
+ actions: {
+ toggleContextColor: () => {
+ const context = getContext();
+ context.color = context.color === 'red' ? 'blue' : 'red';
+ },
+ },
} );
```
+
The `wp-style` directive is executed:
-- When the element is created
-- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
+- When the element is created
+- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
The value received by the directive is used to add or remove the style attribute with the associated CSS property:
-- If the value is `false`, the style attribute is removed: `
`
-- If the value is a string, the attribute is added with its value assigned: `
`
+- If the value is `false`, the style attribute is removed: `
`
+- If the value is a string, the attribute is added with its value assigned: `
`
### `wp-text`
@@ -293,10 +318,10 @@ It sets the inner text of an HTML element.
```html
-
-
+
+
```
@@ -304,21 +329,22 @@ It sets the inner text of an HTML element.
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- toggleContextText: () => {
- const context = getContext();
- context.text = context.text === 'Text 1' ? 'Text 2' : 'Text 1';
- },
- },
+store( 'myPlugin', {
+ actions: {
+ toggleContextText: () => {
+ const context = getContext();
+ context.text = context.text === 'Text 1' ? 'Text 2' : 'Text 1';
+ },
+ },
} );
```
+
The `wp-text` directive is executed:
-- When the element is created
-- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
+- When the element is created
+- Each time there's a change on any of the properties of the `state` or `context` involved in getting the final value of the directive (inside the callback or the expression passed as reference)
The returned value is used to change the inner content of the element: `
value
`.
@@ -340,14 +366,15 @@ This directive runs code on dispatched DOM events like `click` or `keyup`. The s
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- logTime: ( event ) => {
- console.log( new Date() )
- },
- },
+store( 'myPlugin', {
+ actions: {
+ logTime: ( event ) => {
+ console.log( new Date() );
+ },
+ },
} );
```
+
The `wp-on` directive is executed each time the associated event is triggered.
@@ -378,7 +405,7 @@ The syntax of this directive is `data-wp-on-window--[window-event]` (like `data-
See store used with the directive above
```js
-store( "myPlugin", {
+store( 'myPlugin', {
callbacks: {
logWidth() {
console.log( 'Window width: ', window.innerWidth );
@@ -386,6 +413,7 @@ store( "myPlugin", {
},
} );
```
+
The callback passed as the reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`), and the returned value by this callback is ignored. When the element is removed from the DOM, the event listener is also removed.
@@ -414,14 +442,15 @@ The syntax of this directive is `data-wp-on-document--[document-event]` (like `d
See store used with the directive above
```js
-store( "myPlugin", {
+store( 'myPlugin', {
callbacks: {
- logKeydown(event) {
+ logKeydown( event ) {
console.log( 'Key pressed: ', event.key );
},
- },
+ },
} );
```
+
The callback passed as the reference receives [the event](https://developer.mozilla.org/en-US/docs/Web/API/Event) (`event`), and the returned value by this callback is ignored. When the element is removed from the DOM, the event listener is also removed.
@@ -439,13 +468,10 @@ You can attach several side effects to the same DOM element by using the syntax
The `unique-id` doesn't need to be unique globally. It just needs to be different from the other unique IDs of the `wp-watch` directives of that DOM element.
```html
-
-
Counter:
-
-
+
+
Counter:
+
+
```
@@ -453,40 +479,41 @@ The `unique-id` doesn't need to be unique globally. It just needs to be differen
See store used with the directive above
```js
-store( "myPlugin", {
- actions: {
- increaseCounter: () => {
- const context = getContext();
- context.counter++;
- },
- decreaseCounter: () => {
- const context = getContext();
- context.counter--;
- },
- },
- callbacks: {
- logCounter: () => {
- const { counter } = getContext();
- console.log("Counter is " + counter + " at " + new Date() );
- },
- },
+store( 'myPlugin', {
+ actions: {
+ increaseCounter: () => {
+ const context = getContext();
+ context.counter++;
+ },
+ decreaseCounter: () => {
+ const context = getContext();
+ context.counter--;
+ },
+ },
+ callbacks: {
+ logCounter: () => {
+ const { counter } = getContext();
+ console.log( 'Counter is ' + counter + ' at ' + new Date() );
+ },
+ },
} );
```
+
The `wp-watch` directive is executed:
-- When the element is created
-- Each time that any of the properties of the `state` or `context` used inside the callback changes
+- When the element is created
+- Each time that any of the properties of the `state` or `context` used inside the callback changes
The `wp-watch` directive can return a function. If it does, the returned function is used as cleanup logic, i.e., it will run just before the callback runs again, and it will run again when the element is removed from the DOM.
As a reference, some use cases for this directive may be:
-- Logging
-- Changing the title of the page
-- Setting the focus on an element with `.focus()`.
-- Changing the state or context when certain conditions are met
+- Logging
+- Changing the title of the page
+- Setting the focus on an element with `.focus()`.
+- Changing the state or context when certain conditions are met
### `wp-init`
@@ -498,7 +525,7 @@ The `unique-id` doesn't need to be unique globally. It just needs to be differen
```html
-
Hi!
+
Hi!
```
@@ -506,10 +533,10 @@ Here's another example with several `wp-init` directives on the same DOM element
```html
```
@@ -546,7 +573,7 @@ The `unique-id` doesn't need to be unique globally. It just needs to be differen
```html
-
Hi!
+
Hi!
```
@@ -554,38 +581,44 @@ The `unique-id` doesn't need to be unique globally. It just needs to be differen
See store used with the directive above
```js
-import { getElement, store, useState, useEffect } from '@wordpress/interactivity';
+import {
+ getElement,
+ store,
+ useState,
+ useEffect,
+} from '@wordpress/interactivity';
// Unlike `data-wp-init` and `data-wp-watch`, you can use any hooks inside
// `data-wp-run` callbacks.
const useInView = () => {
- const [ inView, setInView ] = useState( false );
- useEffect( () => {
- const { ref } = getElement();
- const observer = new IntersectionObserver( ( [ entry ] ) => {
- setInView( entry.isIntersecting );
- } );
- observer.observe( ref );
- return () => ref && observer.unobserve( ref );
- }, []);
- return inView;
+ const [ inView, setInView ] = useState( false );
+ useEffect( () => {
+ const { ref } = getElement();
+ const observer = new IntersectionObserver( ( [ entry ] ) => {
+ setInView( entry.isIntersecting );
+ } );
+ observer.observe( ref );
+ return () => ref && observer.unobserve( ref );
+ }, [] );
+ return inView;
};
store( 'myPlugin', {
- callbacks: {
- logInView: () => {
- const isInView = useInView();
- useEffect( () => {
- if ( isInView ) {
- console.log( 'Inside' );
- } else {
- console.log( 'Outside' );
- }
- });
- }
- },
+ callbacks: {
+ logInView: () => {
+ const isInView = useInView();
+ useEffect( () => {
+ if ( isInView ) {
+ console.log( 'Inside' );
+ } else {
+ console.log( 'Outside' );
+ }
+ } );
+ },
+ },
} );
```
+
It's important to note that, similar to (P)React components, the `ref` from `getElement()` is `null` during the first render. To properly access the DOM element reference, you typically need to use an effect-like hook such as `useEffect`, `useInit`, or `useWatch`. This ensures that the `getElement()` runs after the component has been mounted and the `ref` is available.
@@ -598,8 +631,8 @@ The key should be a string that uniquely identifies the element among its siblin
```html
-
Item 1
-
Item 2
+
Item 1
+
Item 2
```
@@ -607,8 +640,8 @@ But it can also be used on other elements:
```html
```
@@ -624,9 +657,9 @@ For example, let's consider the following HTML.
```html
-
-
-
+
+
+
```
@@ -634,9 +667,9 @@ It would generate the following output:
```html
-
hello
-
hola
-
olá
+
hello
+
hola
+
olá
```
@@ -644,9 +677,9 @@ The prop that holds the item in the context can be changed by passing a suffix t
```html
-
-
-
+
+
+
```
@@ -655,19 +688,21 @@ By default, it uses each element as the key for the rendered nodes, but you can
For that, you must use `data-wp-each-key` in the `` tag and not `data-wp-key` inside the template content. This is because `data-wp-each` creates a context provider wrapper around each rendered item, and those wrappers are the ones that need the `key` property.
```html
-
-
-
-
+}'
+>
+
+
+
```
@@ -677,12 +712,12 @@ For server-side rendered lists, another directive called `data-wp-each-child` en
```html
-
-
-
-
hello
-
hola
-
olá
+
+
+
+
hello
+
hola
+
olá
```
@@ -693,21 +728,21 @@ The value assigned to a directive is a string pointing to a specific state, acti
In the following example, a getter is used to define the `state.isPlaying` derived value.
```js
-const { state } = store( "myPlugin", {
- state: {
- currentVideo: '',
- get isPlaying() {
- return state.currentVideo !== '';
- }
- },
+const { state } = store( 'myPlugin', {
+ state: {
+ currentVideo: '',
+ get isPlaying() {
+ return state.currentVideo !== '';
+ },
+ },
} );
```
And then, the string value `"state.isPlaying"` is used to assign the result of this selector to `data-wp-bind--hidden`.
```html
-
-
+
+
```
@@ -719,7 +754,7 @@ The example below is getting `state.isPlaying` from `otherPlugin` instead of `my
```html
-
+
@@ -737,35 +772,33 @@ The store is used to create the logic (actions, side effects, etc.) linked to th
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.
+- **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.
```html
+
+
-
-
-
-
-
-
+
+
```
```js
-const { state } = store( "myPlugin", {
- state: {
- someText: "Hello Universe!"
- },
- actions: {
- someAction: () => {
- state.someText // Access or modify global state - "Hello Universe!"
+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!"
- },
- },
-} )
+ const context = getContext();
+ context.someText; // Access or modify local state (context) - "Hello World!"
+ },
+ },
+} );
```
#### Actions
@@ -773,19 +806,19 @@ const { state } = store( "myPlugin", {
Actions are just regular JavaScript functions. Usually triggered by the `data-wp-on` directive (using event listeners) or other actions.
```ts
-const { state, actions } = store("myPlugin", {
- actions: {
- selectItem: ( id ) => {
- const context = getContext();
- // `id` is optional here, so this action can be used in a directive.
- state.selected = id || context.id;
- },
- otherAction: () => {
- // but it can also be called from other actions.
- actions.selectItem(123); // it works and type is correct
- }
- }
-});
+const { state, actions } = store( 'myPlugin', {
+ actions: {
+ selectItem: ( id ) => {
+ const context = getContext();
+ // `id` is optional here, so this action can be used in a directive.
+ state.selected = id || context.id;
+ },
+ otherAction: () => {
+ // but it can also be called from other actions.
+ actions.selectItem( 123 ); // it works and type is correct
+ },
+ },
+} );
```
Async actions
@@ -798,45 +831,46 @@ Imagine a block that has two buttons. One lives inside a context that has `isOpe
```html
-
+
-
+
```
If the action is async and needs to await a long delay.
-- The user clicks the first button.
-- The scope points to the first context, where `isOpen: true`.
-- The first access to `state.isOpen` is correct because `getContext` returns the current scope.
-- The action starts awaiting a long delay.
-- Before the action resumes, the user clicks the second button.
-- The scope is changed to the second context, where `isOpen: false`.
-- The first access to `state.isOpen` is correct because `getContext` returns the current scope.
-- The second action starts awaiting a long delay.
-- The first action finishes awaiting and resumes its execution.
-- The second access to `state.isOpen` of the first action is incorrect, because `getContext` now returns the wrong scope.
+- The user clicks the first button.
+- The scope points to the first context, where `isOpen: true`.
+- The first access to `state.isOpen` is correct because `getContext` returns the current scope.
+- The action starts awaiting a long delay.
+- Before the action resumes, the user clicks the second button.
+- The scope is changed to the second context, where `isOpen: false`.
+- The first access to `state.isOpen` is correct because `getContext` returns the current scope.
+- The second action starts awaiting a long delay.
+- The first action finishes awaiting and resumes its execution.
+- The second access to `state.isOpen` of the first action is incorrect, because `getContext` now returns the wrong scope.
We need to be able to know when async actions start awaiting and resume operations, so we can restore the proper scope, and that's what generators do.
The store will work fine if it is written like this:
+
```js
-const { state } = store("myPlugin", {
- state: {
- get isOpen() {
- return getContext().isOpen;
- },
- },
- actions: {
- someAction: function* () {
- state.isOpen; // This context is correct because it's synchronous.
- yield longDelay(); // With generators, the caller controls when to resume this function.
- state.isOpen; // This context is correct because we restored the proper scope before we resumed the function.
- },
- },
-});
+const { state } = store( 'myPlugin', {
+ state: {
+ get isOpen() {
+ return getContext().isOpen;
+ },
+ },
+ actions: {
+ someAction: function* () {
+ state.isOpen; // This context is correct because it's synchronous.
+ yield longDelay(); // With generators, the caller controls when to resume this function.
+ state.isOpen; // This context is correct because we restored the proper scope before we resumed the function.
+ },
+ },
+} );
```
As mentioned above with [`wp-on`](#wp-on), [`wp-on-window`](#wp-on-window), and [`wp-on-document`](#wp-on-document), an async action should be used whenever the `async` versions of the aforementioned directives cannot be used due to the action requiring synchronous access to the `event` object. Synchronous access is required whenever the action needs to call `event.preventDefault()`, `event.stopPropagation()`, or `event.stopImmediatePropagation()`. To ensure that the action code does not contribute to a long task, you may manually yield to the main thread after calling the synchronous event API. For example:
@@ -844,20 +878,20 @@ As mentioned above with [`wp-on`](#wp-on), [`wp-on-window`](#wp-on-window), and
```js
// Note: In WordPress 6.6 this splitTask function is exported by @wordpress/interactivity.
function splitTask() {
- return new Promise(resolve => {
- setTimeout(resolve, 0);
- });
+ return new Promise( ( resolve ) => {
+ setTimeout( resolve, 0 );
+ } );
}
-store("myPlugin", {
- actions: {
- handleClick: function* (event) {
- event.preventDefault();
- yield splitTask();
- doTheWork();
- },
- },
-});
+store( 'myPlugin', {
+ actions: {
+ handleClick: function* ( event ) {
+ event.preventDefault();
+ yield splitTask();
+ doTheWork();
+ },
+ },
+} );
```
You may want to add multiple such `yield` points in your action if it is doing a lot of work.
@@ -872,97 +906,97 @@ They return a computed version of the state. They can access both `state` and `c
```js
// 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;
- },
- },
+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 in callbacks
-
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:
```js
-const { state, actions } = store( "myPlugin", {
- // ...
+const { state, actions } = store( 'myPlugin', {
+ // ...
} );
```
The `store()` function can be called multiple times and all the store parts will be merged together:
```js
-store( "myPlugin", {
- state: {
- someValue: 1,
- }
+store( 'myPlugin', {
+ state: {
+ someValue: 1,
+ },
} );
-const { state } = store( "myPlugin", {
- actions: {
- someAction() {
- state.someValue // = 1
- }
- }
+const { state } = store( 'myPlugin', {
+ actions: {
+ someAction() {
+ state.someValue; // = 1
+ },
+ },
} );
```
+
All store() calls with the same namespace return the same references, i.e., the same state, actions, etc., containing the result of merging all the store parts passed.
-- To access the context inside an action, derived state, or side effect, you can use the `getContext` function.
-- To access the reference, you can use the `getElement` function.
+- To access the context inside an action, derived state, or side effect, you can use the `getContext` function.
+- To access the reference, you can use the `getElement` function.
```js
-const { state } = store( "myPlugin", {
- state: {
- get someDerivedValue() {
- const context = getContext();
- const { ref } = getElement();
- // ...
- }
- },
- actions: {
- someAction() {
- const context = getContext();
- const { ref } = getElement();
- // ...
- }
- },
- callbacks: {
- someEffect() {
- const context = getContext();
- const { ref } = getElement();
- // ...
- }
- }
+const { state } = store( 'myPlugin', {
+ state: {
+ get someDerivedValue() {
+ const context = getContext();
+ const { ref } = getElement();
+ // ...
+ },
+ },
+ actions: {
+ someAction() {
+ const context = getContext();
+ const { ref } = getElement();
+ // ...
+ },
+ },
+ callbacks: {
+ someEffect() {
+ const context = getContext();
+ const { ref } = getElement();
+ // ...
+ },
+ },
} );
```
This approach enables some functionalities that make directives flexible and powerful:
-- Actions and side effects can read and modify the state and the context.
-- Actions and state in blocks can be accessed by other blocks.
-- Actions and side effects can do anything a regular JavaScript function can do, like access the DOM or make API requests.
-- Side effects automatically react to state changes.
+- Actions and side effects can read and modify the state and the context.
+- Actions and state in blocks can be accessed by other blocks.
+- Actions and side effects can do anything a regular JavaScript function can do, like access the DOM or make API requests.
+- Side effects automatically react to state changes.
### Setting the store
#### On the client side
-*In the `view.js` file of each block* the developer can define both the state and the elements of the store referencing functions like actions, side effects or derived state.
+_In the `view.js` file of each block_ the developer can define both the state and the elements of the store referencing functions like actions, side effects or derived state.
The `store` method used to set the store in JavaScript can be imported from `@wordpress/interactivity`.
@@ -970,21 +1004,21 @@ The `store` method used to set the store in JavaScript can be imported from `@wo
// store
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 }` );
- }
- },
-});
+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 }` );
+ },
+ },
+} );
```
#### On the server side
@@ -1022,43 +1056,54 @@ A given store namespace can be marked as private, thus preventing its content to
```js
const { state } = store(
- "myPlugin/private",
- { state: { messages: [ "private message" ] } },
+ 'myPlugin/private',
+ { state: { messages: [ 'private message' ] } },
{ lock: true }
);
// The following call throws an Error!
-store( "myPlugin/private", { /* store part */ } );
+store( 'myPlugin/private', {
+ /* store part */
+} );
```
There is also a way to unlock private stores: instead of passing a boolean, you can use a string as the `lock` value. Such a string can then be used in subsequent `store()` calls to the same namespace to unlock its content. Only the code knowing the string lock will be able to unlock the protected store namespaced. This is useful for complex stores defined in multiple JS modules.
```js
const { state } = store(
- "myPlugin/private",
- { state: { messages: [ "private message" ] } },
+ 'myPlugin/private',
+ { state: { messages: [ 'private message' ] } },
{ lock: PRIVATE_LOCK }
);
// The following call works as expected.
-store( "myPlugin/private", { /* store part */ }, { lock: PRIVATE_LOCK } );
+store(
+ 'myPlugin/private',
+ {
+ /* store part */
+ },
+ { lock: PRIVATE_LOCK }
+);
```
### Store client methods
Apart from the store function, there are also some methods that allows the developer to access data on their store functions.
- - getContext()
- - getElement()
+- getContext()
+ - getServerContext()
+ - getServerState()
+- getElement()
#### getContext()
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. It can also take an optional namespace argument to retrieve the context of a specific interactive region.
```js
-const context = getContext('namespace');
+const context = getContext( 'namespace' );
```
-- `namespace` (optional): A string that matches the namespace of an interactive region. If not provided, it retrieves the context of the current interactive region.
+
+- `namespace` (optional): A string that matches the namespace of an interactive region. If not provided, it retrieves the context of the current interactive region.
```php
// render.php
@@ -1071,20 +1116,78 @@ const context = getContext('namespace');
// store
import { store, getContext } from '@wordpress/interactivity';
-store( "myPlugin", {
- actions: {
- log: () => {
- const context = getContext();
- // Logs "false"
- console.log('context => ', context.isOpen)
-
- // With namespace argument.
- const myPluginContext = getContext("myPlugin");
- // Logs "false"
- console.log('myPlugin isOpen => ', myPluginContext.isOpen);
- },
- },
-});
+store( 'myPlugin', {
+ actions: {
+ log: () => {
+ const context = getContext();
+ // Logs "false"
+ console.log( 'context => ', context.isOpen );
+
+ // With namespace argument.
+ const myPluginContext = getContext( 'myPlugin' );
+ // Logs "false"
+ console.log( 'myPlugin isOpen => ', myPluginContext.isOpen );
+ },
+ },
+} );
+```
+
+#### getServerContext()
+
+This function is analogous to `getContext()`, but with 2 key differences:
+
+1. Whenever [`actions.navigate()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity-router/#actions) from [`@wordpress/interactivity-router`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity-router/) is called, the object returned by `getServerContext()` is updated. This is useful when you want to update the context of a block based on **new** context coming from the page loaded via `actions.navigate()`. This new context is embedded in the HTML of the page loaded via `actions.navigate()`.
+2. The object returned by `getServerContext()` is read-only.
+
+The server context cannot be directly used in directives, but you can use callbacks to subscribe to its changes.
+
+```js
+const serverContext = getServerContext( 'namespace' );
+```
+
+- `namespace` (optional): A string that matches the namespace of an interactive region. If not provided, it retrieves the server context of the current interactive region.
+
+Example usage:
+
+```js
+store( 'myPlugin', {
+ callbacks: {
+ updateServerContext() {
+ const context = getContext();
+ const serverContext = getServerContext();
+ // Override some property with the new value that came from the server.
+ context.overridableProp = serverContext.overridableProp;
+ },
+ },
+} );
+```
+
+#### getServerState()
+
+Retrieves the server state an interactive region.
+
+This function is serves the same purpose as `getServerContext()`, but it returns the **state** instead of the **context**.
+
+The object returned is read-only, and includes the state defined in PHP with `wp_interactivity_state()`. When using [`actions.navigate()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity-router/#actions) from [`@wordpress/interactivity-router`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity-router/), the object returned by `getServerState()` is updated to reflect the changes in its properties, without affecting the state returned by `store()`. Directives can subscribe to those changes to update the state if needed.
+
+```js
+const serverState = getServerState( 'namespace' );
+```
+
+- `namespace` (optional): A string that matches the namespace of an interactive region. If not provided, it retrieves the server state of the current interactive region.
+
+Example usage:
+
+```js
+const { state } = store( 'myStore', {
+ callbacks: {
+ updateServerState() {
+ const serverState = getServerState();
+ // Override some property with the new value that came from the server.
+ state.overridableProp = serverState.overridableProp;
+ },
+ },
+} );
```
#### getElement()
@@ -1106,15 +1209,15 @@ Those attributes will contain the directives of that element. In the button exam
// store
import { store, getElement } from '@wordpress/interactivity';
-store( "myPlugin", {
- actions: {
- log: () => {
- const element = getElement();
- // Logs attributes
- console.log('element attributes => ', element.attributes)
- },
- },
-});
+store( 'myPlugin', {
+ actions: {
+ log: () => {
+ const element = getElement();
+ // Logs attributes
+ console.log( 'element attributes => ', element.attributes );
+ },
+ },
+} );
```
The code will log:
@@ -1136,7 +1239,7 @@ When the Interactivity API runtime execute callbacks, the scope is set automatic
An example, where `actions.nextImage` would trigger an undefined error without the wrapper:
```js
-store('mySliderPlugin', {
+store( 'mySliderPlugin', {
callbacks: {
initSlideShow: () => {
setInterval(
@@ -1146,8 +1249,8 @@ store('mySliderPlugin', {
3_000
);
},
- },
-})
+ },
+} );
```
## Server functions
@@ -1218,14 +1321,13 @@ const { state } = store( 'myPlugin', {
method: 'POST',
body: formData,
} ).then( ( response ) => response.json() );
- console.log( 'Server data!', data );
- } catch ( e ) {
- // Something went wrong!
- }
- },
+ console.log( 'Server data!', data );
+ } catch ( e ) {
+ // Something went wrong!
+ }
},
- }
-);
+ },
+} );
```
### wp_interactivity_process_directives
@@ -1244,6 +1346,7 @@ echo $processed_html;
```
will output:
+
```html
+
```
diff --git a/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md b/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md
index a2b39fc4c77fb2..f4fcbdf576c1cf 100644
--- a/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md
+++ b/docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md
@@ -692,7 +692,7 @@ In this example, the derived state `state.double` reads from the local context p
### Example: Using derived state with both local context and global state
-Let's now consider a scenario where there are a global tax rate and local product prices and calculate the final price, including tax.
+Let's now consider a scenario where there is a global tax rate and local product prices and calculate the final price, including tax.
```html
get_question_for_page( get_the_ID() ),
+ 'timeLeft' => 5 * 60, // Time to answer all the questions.
+) );
+?>
+```
+
+```javascript
+import { store, getServerState } from '@wordpress/interactivity';
+
+store( 'myPlugin', {
+ actions: {
+ // This action would be triggered by a directive, like:
+ //
+ *nextQuestion() {
+ event.preventDefault( event );
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ actions.navigate( '/question-2' );
+ },
+ },
+ callbacks: {
+ // This callback would be triggered by a directive, like:
+ //
+ updateQuestion() {
+ const serverState = getServerState();
+
+ // Update with the new value coming from the server.
+ // We DON'T want to update `timeLeft` because it represents the time left to answer ALL the questions.
+ state.question = serverState.question;
+ },
+ },
+} );
+```
+
+### `getServerContext()`
+
+`getServerContext()` allows you to subscribe to changes in the **local context** that occur during client-side navigation. This function is analogous to `getServerState()`, but it works with the local context instead of the global state.
+
+Consider a quiz that has multiple questions. Each question is a separate page. When the user navigates to a new question, the server provides the new question and the time left to answer all the questions.
+
+```php
+ get_question_for_page( get_the_ID() ),
+) );
+?>
+```
+
+```javascript
+import { store, getServerContext } from '@wordpress/interactivity';
+
+store( 'myPlugin', {
+ actions: {
+ // This action would be triggered by a directive, like:
+ //
+ *nextQuestion() {
+ event.preventDefault( event );
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ actions.navigate( '/question-2' );
+ },
+ },
+ callbacks: {
+ // This callback would be triggered by a directive, like:
+ //
+ updateQuestion() {
+ const serverContext = getServerContext();
+ const context = getContext();
+
+ // Update with the new value coming from the server.
+ context.currentQuestion = serverContext.currentQuestion;
+ },
+ },
+} );
+```
+
+### When to Use
+
+Whenever you have interactive blocks that rely on global state that may change due to navigation events, ensuring consistency across different parts of your application.
+
+### Best Practices for using `getServerState()` and `getServerContext()`
+
+- **Read-Only References:** Both `getServerState()` and `getServerContext()` return read-only objects. You can use those objects to update the global state or local context.
+- **Callback Integration:** Incorporate these functions within your store [callbacks](/docs/reference-guides/interactivity-api/api-reference.md#accessing-data-in-callbacks) to react to state and context changes.
+
## Conclusion
-Remember, the key to effective state management is to keep your state minimal and avoid redundancy. Use derived state to compute values dynamically, and choose between global state and local context based on the scope and requirements of your data. This will lead to a cleaner, more robust architecture that is easier to debug and maintain.
+Remember, the key to effective state management is to keep your state minimal and avoid redundancy. Use derived state to compute values dynamically, and choose between global state and local context based on the scope and requirements of your data. This will lead to a cleaner, more robust architecture that is easier to debug and maintain. Finally, if you need to synchronize the state or context with the server, you can use `getServerState()` and `getServerContext()` to achieve this.