diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
index 25d2bbc692efdf..6b20b2dba8376e 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
@@ -170,3 +170,29 @@
+
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
index 0fab8b1862c608..9437edced74a44 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
@@ -88,3 +88,21 @@ const { actions } = store( 'directive-context-navigate', {
},
},
} );
+
+store( 'directive-context-watch', {
+ actions: {
+ increment: () => {
+ const ctx = getContext();
+ ctx.counter = ctx.counter + 1;
+ },
+ },
+ callbacks: {
+ countChanges: () => {
+ const ctx = getContext();
+ // Subscribe to changes in counter.
+ // eslint-disable-next-line no-unused-expressions
+ ctx.counter;
+ ctx.changes = ctx.changes + 1;
+ },
+ },
+});
diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js
index 67ad50a4595807..d04c1a1f74d3ed 100644
--- a/packages/interactivity/src/directives.js
+++ b/packages/interactivity/src/directives.js
@@ -39,13 +39,12 @@ const descriptor = Reflect.getOwnPropertyDescriptor;
const proxifyContext = ( current, inherited = {} ) =>
new Proxy( current, {
get: ( target, k ) => {
- // Subscribe to the inherited and current props.
- const inheritedProp = inherited[ k ];
+ // Always subscribe to prop changes in the current context.
const currentProp = target[ k ];
// Return the inherited prop when missing in target.
if ( ! ( k in target ) && k in inherited ) {
- return inheritedProp;
+ return inherited[ k ];
}
// Proxify plain objects that are not listed in `ignore`.
@@ -54,11 +53,15 @@ const proxifyContext = ( current, inherited = {} ) =>
! contextAssignedObjects.get( target )?.has( k ) &&
isPlainObject( peek( target, k ) )
) {
- return proxifyContext( currentProp, inheritedProp );
+ return proxifyContext( currentProp, inherited[ k ] );
}
- // For other cases, return the value from target.
- return currentProp;
+ /*
+ * For other cases, return the value from target, also subscribing
+ * to changes in the parent context when the current prop is
+ * not defined.
+ */
+ return k in target ? currentProp : inherited[ k ];
},
set: ( target, k, value ) => {
const obj =
diff --git a/test/e2e/specs/interactivity/directive-context.spec.ts b/test/e2e/specs/interactivity/directive-context.spec.ts
index 3a566e7c2d9619..85341774c2af4e 100644
--- a/test/e2e/specs/interactivity/directive-context.spec.ts
+++ b/test/e2e/specs/interactivity/directive-context.spec.ts
@@ -313,4 +313,27 @@ test.describe( 'data-wp-context', () => {
await select2.click();
await expect( selected ).toHaveText( 'Text 2' );
} );
+
+ test( 'should not subscribe to parent context props if those also exist in child', async ( {
+ page,
+ } ) => {
+ const counterParent = page.getByTestId( 'counter parent' );
+ const counterChild = page.getByTestId( 'counter child' );
+ const changes = page.getByTestId( 'counter changes' );
+
+ await expect( counterParent ).toHaveText( '0' );
+ await expect( counterChild ).toHaveText( '0' );
+ // The first render counts, so the changes counter starts at 1.
+ await expect( changes ).toHaveText( '1' );
+
+ await counterParent.click();
+ await expect( counterParent ).toHaveText( '1' );
+ await expect( counterChild ).toHaveText( '0' );
+ await expect( changes ).toHaveText( '1' );
+
+ await counterChild.click();
+ await expect( counterParent ).toHaveText( '1' );
+ await expect( counterChild ).toHaveText( '1' );
+ await expect( changes ).toHaveText( '2' );
+ } );
} );