Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity API: Support for the data-wp-key directive #53844

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"apiVersion": 2,
"name": "test/directive-key",
"title": "E2E Interactivity tests - directive key",
"category": "text",
"icon": "heart",
"description": "",
"supports": {
"interactivity": true
},
"textdomain": "e2e-interactivity",
"viewScript": "directive-key-view",
"render": "file:./render.php"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
/**
* HTML for testing the directive `data-wp-key`.
*
* @package gutenberg-test-interactive-blocks
*/

Check failure on line 6 in packages/e2e-tests/plugins/interactive-blocks/directive-key/render.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

There must be exactly one blank line after the file comment
?>

<div data-wp-interactive data-wp-navigation-id="some-id">
<ul>
<li data-wp-key="id-2" data-testid="first-item">2</li>
<li data-wp-key="id-3">3</li>
</ul>
<button data-testid="navigate" data-wp-on--click="actions.navigate">
Navigate
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
( ( { wp } ) => {
const { store, navigate } = wp.interactivity;

const html = `
<div data-wp-interactive data-wp-navigation-id="some-id">
<ul>
<li data-wp-key="id-1">1</li>
<li data-wp-key="id-2" data-testid="second-item">2</li>
<li data-wp-key="id-3">3</li>
</ul>
</div>`;

store( {
actions: {
navigate: () => {
navigate( window.location, {
force: true,
html,
} );
},
},
} );
} )( window );
1 change: 1 addition & 0 deletions packages/interactivity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### New Features

- Support keys using `data-wp-key`. ([#53844](https://github.com/WordPress/gutenberg/pull/53844))
- Support region-based client-side navigation. ([#53733](https://github.com/WordPress/gutenberg/pull/53733))
- Allow passing optional `afterLoad` callbacks to `store` calls. ([#53363](https://github.com/WordPress/gutenberg/pull/53363))

Expand Down
27 changes: 27 additions & 0 deletions packages/interactivity/docs/2-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ DOM elements are connected to data stored in the state & context through directi
- [`wp-on`](#wp-on) ![](https://img.shields.io/badge/EVENT_HANDLERS-afd2e3.svg)
- [`wp-effect`](#wp-effect) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg)
- [`wp-init`](#wp-init) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg)
- [`wp-key`](#wp-key) ![](https://img.shields.io/badge/TEMPLATING-afd2e3.svg)
- [Values of directives are references to store properties](#values-of-directives-are-references-to-store-properties)
- [The store](#the-store)
- [Elements of the store](#elements-of-the-store)
Expand Down Expand Up @@ -449,6 +450,32 @@ store( {

The `wp-init` can return a function. If it does, the returned function will run when the element is removed from the DOM.

#### `wp-key`

Here is the text for the `wp-key` directive description:
luisherranz marked this conversation as resolved.
Show resolved Hide resolved

The `wp-key` directive assigns a unique key to an element to help the Interactivity API identify it when iterating through arrays of elements. This becomes important if your array elements can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key value helps the Interactivity API infer what exactly has changed in the array, allowing it to make the correct updates to the DOM.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This becomes important if your array elements can move (e.g. due to sorting), get inserted, or get deleted.

Is there any example we can add here to understand better the value of data-wp-key? I mean, some example that actually rearranges elements, insert them or remove them where having a key is essential.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I think it's useful only when doing client-side navigation. So maybe we can add that in #53733.

Ok, that makes sense. If you don't mind, let's merge this as it is because it's merged against #53733, and let's add a better example of data-wp-key in the docs for that PR.


The key should be a string that uniquely identifies the element among its siblings. Typically it is used on repeated elements like list items. For example:

```html
<ul>
<li data-wp-key="unique-id-1">Item 1</li>
<li data-wp-key="unique-id-2">Item 2</li>
</ul>
```

But it can also be used on other elements:

```html
<div>
<a data-wp-key="previous-page" ...>Previous page</a>
<a data-wp-key="next-page" ...>Next page</a>
</div>
```

When the list is re-rendered, the Interactivity API will match elements by their keys to determine if an item was added/removed/reordered. Elements without keys might be recreated unnecessarily.

### Values of directives are references to store properties

The value assigned to a directive is a string pointing to a specific state, selector, action, or effect. *Using a Namespace is highly recommended* to define these elements of the store.
Expand Down
1 change: 1 addition & 0 deletions packages/interactivity/src/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ options.vnode = ( vnode ) => {
if ( vnode.props.__directives ) {
const props = vnode.props;
const directives = props.__directives;
if ( directives.key ) vnode.key = directives.key.default;
delete props.__directives;
const priorityLevels = getPriorityLevels( directives );
if ( priorityLevels.length > 0 ) {
Expand Down
34 changes: 34 additions & 0 deletions test/e2e/specs/interactivity/directive-key.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Internal dependencies
*/
import { test, expect } from './fixtures';

test.describe( 'data-wp-key', () => {
test.beforeAll( async ( { interactivityUtils: utils } ) => {
await utils.activatePlugins();
await utils.addPostWithBlock( 'test/directive-key' );
} );

test.beforeEach( async ( { interactivityUtils: utils, page } ) => {
await page.goto( utils.getLink( 'test/directive-key' ) );
} );

test.afterAll( async ( { interactivityUtils: utils } ) => {
await utils.deactivatePlugins();
await utils.deleteAllPosts();
} );

test( 'should keep the elements when adding items to the start of the array', async ( {
page,
} ) => {
// Add a number to the node so we can check later that it is still there.
await page
.getByTestId( 'first-item' )
.evaluate( ( n ) => ( ( n as any )._id = 123 ) );
await page.getByTestId( 'navigate' ).click();
const id = await page
.getByTestId( 'second-item' )
.evaluate( ( n ) => ( n as any )._id );
expect( id ).toBe( 123 );
} );
} );
Loading