You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Changes the way getters are updated when store() is called. Instead of synchronously, which can cause problems with stores that are loaded after hydration, they are now updated asynchronously in a microtask, ensuring the updates happen before Preact renders.
In the case that a getter is accessed synchronously right after being updated, the pending update is consolidated at that moment to reflect the correct value.
The store() function of the interactivity API is intended to allow the following syntax:
import{store}from'@wordpress/interactivity';const{ state }=store('myPlugin',{state: {number: 2,getdouble(){returnstate.number*2;},},});
See how the returned state is used inside the state.double getter. When the store() function runs before hydration, it works just fine. But if it happens after hydration, and the state.double was already accessed, meaning that a computed was already created for that prop, the getter is evaluated before store() returns, and the execution fails with the following error:
ReferenceError: Cannot access 'state' before initialization
This happens because of the Preact's computed() implementation, which updates all computeds synchronously when any of their sources change.
How?
Instead of creating a custom computed() implementation, this PR adds a setGetterAsync method to PropSignal (the internal class that handles prop updates). That method makes PropSignal getters to be updated in a microtask. As microtasks run right at the end of the current task, the update is ensured to happen before the next Preact render (scheduled for the next task).
In addition, if the getComputed() function of the PropSignal class is called and a pending getter update exists, the update is performed synchronously at that moment.
Testing Instructions
Apart from the added unit tests and e2e tests, you can test the fix manually following these steps:
In a page or template with interactive blocks, add a Custom HTML block with this content:
Custom HTML
<divdata-wp-interactive="myPlugin"><buttondata-wp-on--click="actions.inc">+</button><p>Count: <spandata-wp-text="state.count"></span></p><p>Double: <spandata-wp-text="state.double"></span></p></div><scripttype=module>/** * WordPress dependencies */import{store}from'@wordpress/interactivity';// Simulate state serialized from the server.store('myPlugin',{state: {count: 1,double: 2,},});// Simulate lazy loaded store.setTimeout(()=>{const{ state }=store('myPlugin',{state: {getdouble(){returnstate.count*2;},},actions: {inc: ()=>{state.count++;},},});});</script>
Save and visit the page with the Custom HTML block.
A button with the symbol + should be rendered, along with the words "Count" and "Double", each with its corresponding value.
Press the button + a couple of times.
Ensure the value of "Double" is consistent with the value of "Count".
Flaky tests detected in d6a46b5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.
DAreRodz
changed the title
iAPI: Make state getters to be updated asynchronously
iAPI: Make state getters to be updated asynchronously with store()Aug 1, 2025
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.
If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Closes #70874
Changes the way getters are updated when
store()
is called. Instead of synchronously, which can cause problems with stores that are loaded after hydration, they are now updated asynchronously in a microtask, ensuring the updates happen before Preact renders.In the case that a getter is accessed synchronously right after being updated, the pending update is consolidated at that moment to reflect the correct value.
Why?
See #70874.
The
store()
function of the interactivity API is intended to allow the following syntax:See how the returned
state
is used inside thestate.double
getter. When thestore()
function runs before hydration, it works just fine. But if it happens after hydration, and thestate.double
was already accessed, meaning that a computed was already created for that prop, the getter is evaluated beforestore()
returns, and the execution fails with the following error:This happens because of the Preact's
computed()
implementation, which updates all computeds synchronously when any of their sources change.How?
Instead of creating a custom
computed()
implementation, this PR adds asetGetterAsync
method toPropSignal
(the internal class that handles prop updates). That method makesPropSignal
getters to be updated in a microtask. As microtasks run right at the end of the current task, the update is ensured to happen before the next Preact render (scheduled for the next task).In addition, if the
getComputed()
function of thePropSignal
class is called and a pending getter update exists, the update is performed synchronously at that moment.Testing Instructions
Apart from the added unit tests and e2e tests, you can test the fix manually following these steps:
In a page or template with interactive blocks, add a Custom HTML block with this content:
Custom HTML
Save and visit the page with the Custom HTML block.
A button with the symbol
+
should be rendered, along with the words "Count" and "Double", each with its corresponding value.Press the button
+
a couple of times.Ensure the value of "Double" is consistent with the value of "Count".