Overview
#3487484: Save page data form values in application state with support for undo/redo didn't address media fields: the selected image is not stored in the application state. Selection is currently possible for newly added images. The form control to remove the selection is also missing. There are e2e tests that indicate this is no longer an issue
However there was a bug reported in #6 occurring for @lauriii Perhaps this issue is now home to that, and it can be set back to active once it's been firgured what is different from the that e2e and manual testing where it is working.
The issue we ran into might also be present in the component instance form, but the times we've run into it, this has occurred in page data.
When the media library widget renders in a page data form, there is a breif time where the "Add media" element is clickable despite not being fully intitialized and instead of triggering the media library dialog we are brought to a new page with the following error
{"message":"Missing required argument \u0022ajax_form\u0022 for Request [post \/xb\/api\/v0\/form\/content-entity\/{entityTypeId}\/{entityId}\/{entityFormMode}]"}
The easiest way to reproduce is to throttle CPU and refresh the page with the cursor near where the media library widget will eventually appear. Click "Add media" as quickly as possible once it appears, and it will likely result in the error.
If we ensure the element can't be interacted with before it's fully AJAX empowered, this should be fine. How this interaction is prevented can still be figured out - do we simply disable the element? Perhaps the opacity is reduced as well? Do we include a throbber? We'll come up with something cool.
Proposed resolution
| Comment | File | Size | Author |
|---|---|---|---|
| #20 | Screenshot From 2025-07-07 13-16-54.png | 45.55 KB | larowlan |
| #18 | Screenshot From 2025-07-07 12-42-37.png | 347.88 KB | larowlan |
| #15 | Screenshot From 2025-07-07 12-21-12.png | 192.46 KB | larowlan |
| #6 | CleanShot 2025-06-04 at 15.25.34.mp4 | 25.22 MB | lauriii |
Issue fork experience_builder-3494581
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
balintbrewsComment #3
wim leersFeel free to ping me when you dig into this; @bnjmnm is the real expert in that area, but happy to assist when @bnjmnm has more important things to tackle!
Comment #4
lauriiiComment #5
bnjmnmNo longer relevant as this was addressed by other work. There is confirmation of this in the test
'Can open the media library widget in an xb_page props form'in media-library.cy.js (this test does more than justopen, it adds / removes etc.)Comment #6
lauriiiMaybe there's something more specific that's wrong but this doesn't seem to be working for me? Attached video to show what I'm seeing.
Comment #7
lauriiiComment #8
bnjmnmClearly a problem is occurring in #7 but it isn't one I'm running into (see this video) + this and the e2e test mentioned in #5 demonstrate that "Handl[ing] media image fields on page data form" currently works.
The error in #6 looks like it's coming from OpenAPI validation, which should obviously be addressed. If anyone currently experiencing it can either update this issue summary or create a new issue targeting that specific bug
Comment #9
bnjmnmComment #10
bnjmnm@lauriii
Comment #11
larowlanComment #12
larowlanFor me I can reproduce this regardless of how long I wait for things to initialize.
However, when I uninstall xb_vite module it no longer occurs.
Cypress tests don't use xb_vite.
Can this be reproduced without xb_vite? i.e. in a production setting?
Comment #13
balintbrews#12: I'm seeing something very similar in #3533703-3: Calling the `setPageData` action creator directly doesn't update values in the page data form where I wrote a fix, but the double rendering done by
<StrictMode>breaks it, which is what happens when you run the app viaxb_viteand Vite's dev server. I'm a bit afraid this is exposing a bug ininputBehaviors.Comment #14
larowlanSaw this happen without xb_vite
Comment #15
larowlanWhen this happens, no amount of waiting helps, which seems to point to a race condition.
In the failure case the
data-once=drupal-ajaxattribute is missing.This comes from
Drupal.behaviors.AJAXwhich we load as theexperience_builder/xb.drupal.ajaxlibraryHowever, our custom ajax commands don't have a dependency on this, so could load before the base ajax has.
I added that dependency and in my testing this seems to work.
I also added some subtle CSS changes so that the user can't click the button until the behavior has been attached, including a greyed out state

Comment #17
larowlanWas able to still hit this, so will dig further into the race condition
Comment #18
larowlanThe behavior not attached is a red-herring - here you can see the available Drupal.behaviors and AJAX is there but the button stays greyed out - meaning
data-once="drupal-ajax"didn't get appliedComment #19
larowlanDebugging this further I can only get this to occur on a hard-reload which does point towards JS files being fully loaded
Comment #20
larowlanWhen this happens, the jQuery selector for the ajax element in drupalSettings.ajax doesn't find the element, which probably indicates it is re-rendering
Comment #21
larowlanI was able to get this to a point where I could no-longer reproduce it, even with a hard-reload.
The tl;dr was that we were calling
useDrupalBehaviorsonce we had HTML for the form and the outer div (ref) had been rendered. But this didn't mean the inner form had rendered (just that we had the HTML) and as a result the this selector in Drupal's AJAX was unable to find the element to attach the behavior too.This change ensures the behaviors are attached once the form HTML has been rendered.
Comment #22
wim leersLooks like @larowlan is confident this was a race condition between our logic and the AJAX system (independently) executing. That seems very plausible given all other comments in this issue!
I like the elegance of the solution, but I know
littlenot a single thing about "refs", so deferring to @bnjmnm.I would like to see this crucial comment on the MR moved into the code, and ideally, generalized to also apply to the component instance form, where AJAX behaviors are also a thing.
Would be a shame to solve the same problem two times.
Comment #23
bnjmnmChanging status / assigned to reflect the changes @jessebaker requested in the MR
Comment #25
wim leersThanks, both!
Comment #26
larowlanIf I make the same change to component inputs form, it stops working there the same way as the original report here 😭
If I change the way
useDrupalBehaviorsworks to passref.currentas a dependency instead of justrefit fixes the component inputs form but breaks the page form(╯°□°)╯︵ ┻━┻Comment #27
larowlanOK I think I've found a solution that works for both forms.
I think the issue w.r.t race conditions comes down to how useEffect works - there is no guarantee that the browser has painted by the time the effect is called - which is why we were using
setTimeoutin theuseDrupalBehaviorshook.But we were also setting the HTML inside a
useEffecthook but I don't think we need that extra layer of reactivity, because it is already provided by RTKquery andhyperscriptifyandparseHyperscriptifyTemplateare synchronous.Removing the
setState/useStateto track the form HTML and just relying on the reactivity provided by redux seems to fix the issue reported here and in a way that I can apply the same fix to the component inputs form without breaking it..Comment #28
larowlanOk, 32 test fails tells me that idea won't fly
Comment #29
larowlanWoot got to the bottom of why this only impacts page data form
We don't render inputs on the page data form until page data exists
This was added in #3521213: Page data form inputs should not render until page data exists
So this is why we were attaching behaviors but jQuery wasn't finding the elements.
In PageDataForm the condition that the jsx form data exists was satisfied, but the elements weren't being rendered because of that guard in
inputBehaviorsand hence jQuery wasn't finding them.So the race condition is as follows:
The fix is much simpler now - was glad I could work that one out 🤯 - basically we hoist that guard out of each individual element and only check it on the outer form renderer. It achieves the same result but ensures that when the parent form ref renders, the children inside it actually render as well and therefore jQuery can attach the behaviors.
I also added some timeout clean up which was missing and retained the CSS changes because I think they're useful still.
Comment #30
wim leersSounds like this is definitely ready for a new @jessebaker review!
Comment #32
bnjmnmThat solution makes sense, to move the page data loaded check to a more sensible place. Nice.
Comment #33
mayur-sose commentedHi Team, I have verified below scenarios and those are working as expected :
Comment #34
wim leersThanks! :)