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

wp-each "Cannot access 'state' before initialization" #69266

Open
3 of 6 tasks
poof86 opened this issue Feb 20, 2025 · 3 comments
Open
3 of 6 tasks

wp-each "Cannot access 'state' before initialization" #69266

poof86 opened this issue Feb 20, 2025 · 3 comments
Labels
[Feature] Interactivity API API to add frontend interactivity to blocks. [Type] Bug An existing feature does not function as intended

Comments

@poof86
Copy link

poof86 commented Feb 20, 2025

Description

I've updated gutenberg from a v18... to the current v20.2.0 and it seems something has changed with how the state is initialised before directives are parsed.

My wp-each directive referencing a state derived getter wasn't a problem before. Now when the wp-each directive is parsed and calls a getter on the state that uses the state variable I'm getting the error: "ReferenceError: Cannot access 'state' before initialization"

I had to wrap the first access to the state in the getter with a try catch to find out this was the problem.

Step-by-step reproduction instructions

  • Create a state getter in which you reference the state
  • Use this getter in a data-wp-each
  • Wrap the line where you access the state in the getter with a try..catch to catch the error

Screenshots, screen recording, code snippet

Example:

view.js state getter snippet

const { state } = store('mynamespace/query', {
state: {
	get terms() {
        	try {
			const { taxonomy : taxonomyQuery = {} } = state;
			const { taxonomy } = getContext();
			return taxonomyQuery[taxonomy] ?? [];
        	} catch (error) {
			console.log(error);
			return [];
		}
	}
}

render.php snippet

<template data-wp-each="state.terms">
	<a 
		data-wp-text="state.termLabel"
	></a>
</template>

Environment info

Wordpress 6.7.2
Gutenberg 20.2.0
Custom theme

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure
@poof86 poof86 added the [Type] Bug An existing feature does not function as intended label Feb 20, 2025
@poof86
Copy link
Author

poof86 commented Feb 20, 2025

I've been trying to figure out long before posting this issue why my code stopped working, and of course just after posting it I found a (temporary) solution. Simply get the state reference before creating your actual state implementation:

const { state } = store('mynamespace/query');

store('mynamespace/query', {
	 state: {
	 	 get terms() {
...

But this shouldn't be needed right?

@t-hamano t-hamano added the [Feature] Interactivity API API to add frontend interactivity to blocks. label Feb 26, 2025
@luisherranz
Copy link
Member

I'm unable to reproduce with 6.18.0 (current version shipped in 20.3) or 6.17.0 (version shipped in 20.2).

Could you please modify this Stackblitz template until you reproduce the problem?

If you don't want to use StackBlitz, then please create a new block and try to reproduce it there.

@poof86
Copy link
Author

poof86 commented Mar 9, 2025

Ok, after a lot of testing I figured out what the issue is I'm experiencing.

Please take the time to read this while I try to explain, hopefully it will create a clear picture of what I'm trying to achieve and where it is going wrong.

I really like how quickly you can share store data between blocks on the front-end and have blocks respond to state changes made by other blocks. I've made some "complex" systems with it, where on the back-end the interactivity state also changes and the navigation router updates the blocks perfectly without needing a page reload. love it!

A lot of my blocks use state derived getters that are influenced by the interactivity context they have. This is really useful in the wp-each directive, where context data needs to be combined with other state data. This way different blocks can use the same interactivity state derived getters, making for very efficient code.

Instead of duplicating the code of these getters for every block view script, I gathered the shared getters into a separate script that is loaded as a dependency for these view scripts. And this is where things go wrong.

When the script module with the shared state is loaded without being enqueued (by wp_script_modules), the script is added to be preloaded in the header as and added to the importmap. I'm able to confirm that the script is loaded correctly and is used by the different blocks view script. BUT, it works sometimes, which I think indicates there is a loading order problem.

I even exported and imported the interactivity state from the shared script module into the block view scripts, hoping the browser would understand the dependencies between all the module scripts and load them in the correct order.

So just enqueue the script module when it is needed? This also didn't work. When a script module is enqueued, it is added to the header similar to block view script modules as <script type="module" src="link_to_script"></script>. But the error in this git issue still occurs. While the actual getter on the shared state was called by wp-directives, using the state in these getters for state derived data caused an error to be thrown.

I did finally find a fix (but I don't think it's a solution), if the shared script module is registered before all the blocks view script modules (enqueue order doesn't matter), the shared module script is place before the blocks view <script type="module"... tags in the header. This resolves this git issue and the random loading crash of the Interactivity API on the front-end on page load for me.

This means for now if you want to share an Interactivity state with reusable methods between blocks, you will have to register the script module before the blocks are registered in Gutenberg and enqueue it, instead of working with dependencies.

You could also create a "context" block container with the shared interactivity state in its view script module, that houses the blocks that use this shared interactivity state. I haven't tested this, but this way the shared script module should always be loaded first.

But this also raises some questions:

  • Are the script modules not loaded/executed in the correct order when imports/dependencies are being used?
  • Are they loaded in order, but take to long importing other script modules, causing @wordpress/interactivity to be executed before the states are updated by the scripts using store('namespace', {...})?
  • Can wp-directives work with interactivity states that are preloaded?

I might not be understanding everything about how modules work in the browser, but it would be nice if there is an official way of sharing code between interactivity states to prevent redundant code in my projects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Interactivity API API to add frontend interactivity to blocks. [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

No branches or pull requests

3 participants