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

Command icon 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

balintbrews created an issue. See original summary.

balintbrews’s picture

Title: [PP-1] Handle media image fields on page data form » Handle media image fields on page data form
Status: Postponed » Active
wim leers’s picture

Feel 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!

lauriii’s picture

Issue tags: +beta blocker
bnjmnm’s picture

Status: Active » Closed (outdated)

No 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 just open, it adds / removes etc.)

lauriii’s picture

StatusFileSize
new25.22 MB

Maybe 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.

lauriii’s picture

Status: Closed (outdated) » Active
bnjmnm’s picture

Clearly 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

bnjmnm’s picture

Title: Handle media image fields on page data form » Something regarding media image fields on page data form
Issue summary: View changes
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs steps to reproduce
bnjmnm’s picture

Title: Something regarding media image fields on page data form » Add Media button in page data form is clickable before fully initialized
Issue summary: View changes
Status: Postponed (maintainer needs more info) » Active
Issue tags: -Needs steps to reproduce

@lauriii

larowlan’s picture

Issue tags: +sprint
larowlan’s picture

Status: Active » Postponed (maintainer needs more info)

For 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?

balintbrews’s picture

#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 via xb_vite and Vite's dev server. I'm a bit afraid this is exposing a bug in inputBehaviors.

larowlan’s picture

Status: Postponed (maintainer needs more info) » Active

Saw this happen without xb_vite

larowlan’s picture

Status: Active » Needs review
StatusFileSize
new192.46 KB

When this happens, no amount of waiting helps, which seems to point to a race condition.
In the failure case the data-once=drupal-ajax attribute is missing.

This comes from Drupal.behaviors.AJAX which we load as the experience_builder/xb.drupal.ajax library

However, 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

larowlan’s picture

Status: Needs review » Needs work

Was able to still hit this, so will dig further into the race condition

larowlan’s picture

StatusFileSize
new347.88 KB

The 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 applied

larowlan’s picture

Debugging this further I can only get this to occur on a hard-reload which does point towards JS files being fully loaded

larowlan’s picture

StatusFileSize
new45.55 KB

When this happens, the jQuery selector for the ajax element in drupalSettings.ajax doesn't find the element, which probably indicates it is re-rendering

larowlan’s picture

Status: Needs work » Needs review

I 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 useDrupalBehaviors once 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.

wim leers’s picture

Assigned: Unassigned » bnjmnm
Status: Needs review » Reviewed & tested by the community
Issue tags: +race condition, +Ajax

Looks 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 little not 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.

bnjmnm’s picture

Assigned: bnjmnm » Unassigned
Status: Reviewed & tested by the community » Needs work

Changing status / assigned to reflect the changes @jessebaker requested in the MR

wim leers’s picture

Assigned: Unassigned » larowlan

Thanks, both!

larowlan’s picture

Assigned: larowlan » Unassigned
Status: Needs work » Needs review

If 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 useDrupalBehaviors works to pass ref.current as a dependency instead of just refit fixes the component inputs form but breaks the page form

(╯°□°)╯︵ ┻━┻

larowlan’s picture

OK 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 setTimeout in the useDrupalBehaviors hook.

But we were also setting the HTML inside a useEffect hook but I don't think we need that extra layer of reactivity, because it is already provided by RTKquery and hyperscriptify and parseHyperscriptifyTemplate are synchronous.

Removing the setState/useState to 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..

larowlan’s picture

Status: Needs review » Needs work

Ok, 32 test fails tells me that idea won't fly

larowlan’s picture

Status: Needs work » Needs review

Woot 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 inputBehaviors and hence jQuery wasn't finding them.

So the race condition is as follows:

  • HTML for the page data form has been loaded and jsxFormContent exists
  • The layout has not been, so pageData does not

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.

wim leers’s picture

Assigned: Unassigned » jessebaker

Sounds like this is definitely ready for a new @jessebaker review!

  • bnjmnm committed 5d014b04 on 0.x authored by larowlan
    Issue #3494581 by larowlan, bnjmnm, jessebaker, lauriii, balintbrews,...
bnjmnm’s picture

Assigned: jessebaker » Unassigned
Status: Needs review » Fixed

That solution makes sense, to move the page data loaded check to a more sensible place. Nice.

mayur-sose’s picture

Hi Team, I have verified below scenarios and those are working as expected :

ID Test Scenario Steps Expected Result Pass/Fail
TC1 Add Media button is disabled until fully initialised
  1. Open the page data form containing the media library widget.
  2. Throttle CPU/network in your browser's dev tools.
  3. Refresh or navigate to the page.
  4. Observe the "Add Media" button as soon as it appears.
The "Add Media" button is visibly disabled/not clickable (dimmed, greyed out, or shows a loading indicator) until full initialisation. Pass
TC2 Clicking Add Media quickly during load does not trigger error
  1. Throttle CPU/network.
  2. Refresh/navigate to the page and, as soon as "Add Media" appears, click it as quickly as possible.
  3. Observe the result.
No error is triggered; the button either shows a loading indicator or does nothing until it is fully enabled. Pass
TC3 Add Media button becomes enabled only when ready
  1. Wait until the media widget is fully loaded.
  2. Confirm that "Add Media" becomes enabled/interactive only when initialisation is complete.
Once initialised, the button is fully enabled and interactive; clicking it launches the media library dialog as expected. Pass
TC4 No page navigation or backend error occurs on premature click
  1. Repeat TC2.
  2. If able to click early, check if the application navigates away or shows an error message.
User is not redirected; no error page or { "message":"Missing required argument..." } error is shown. Pass
TC5 Visual feedback (opacity/throbber) shown while initialising (if implemented)
  1. Load page data form and quickly observe the Add Media button.
  2. Note any loading spinner, reduced opacity, or "please wait" indicator.
Button displays clear visual cue (spinner, lower opacity, or similar) while disabled/not fully ready. Pass
TC6 Add Media button behaves correctly on repeated quick refreshes or navigation to XB
  1. Cmd+R or Ctrl+R refresh repeatedly and try to click the button each time as soon as it appears.
  2. Note any erroneous outcomes.
Button never allows interaction until ready, regardless of fast refresh or navigation speed. Pass
TC7 Media addition via the button works as expected post-initialisation
  1. After page/widget fully loads, click "Add Media".
Expected dialog opens and user can attach/select media as intended without errors. Pass
TC8 Issue does not reproduce in component instance form (regression check, if relevant)
  1. Throttle CPU/network and attempt to click "Add media" quickly.
  2. Observe behavior.
Button remains disabled until ready, with no premature click or error, matching the expected behavior of the page data form. Pass
wim leers’s picture

Issue tags: -sprint

Thanks! :)

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.