Problem/Motivation

After an AJAX form rebuild (for example after submitting a form with validation errors), the DropzoneJS widget is initialized twice. The old Dropzone instance is not destroyed and remains registered alongside the newly created instance.

Steps to reproduce

  1. Add a DropzoneJS field to an AJAX-enabled Drupal form.
  2. Upload a file.
  3. Submit the form in a way that triggers an AJAX validation error.
  4. Inspect the Dropzone instances in the browser console:
Dropzone.instances.map(function (dz) {
  return {
    id: dz.element.id,
    files: dz.files.length
  };
});

Expected result

Only one Dropzone instance exists for the widget element.

Actual result

Multiple Dropzone instances exist after the AJAX rebuild, for example:

[
  {
    id: "dropzone-widget-id",
    files: 1
  },
  {
    id: "dropzone-widget-id--ajax-suffix",
    files: 1
  }
]

The old instance remains registered while a new instance is created for the rebuilt form element.

Impact

The Dropzone state becomes inconsistent. Actions such as removing uploaded files may stop working because multiple Dropzone instances manage the same field lifecycle.

Additional debugging information

Before the AJAX rebuild:
Dropzone.instances.length === 1

After the AJAX rebuild:
Dropzone.instances.length === 2

Both instances contain the uploaded file.

The current cleanup relies on drupalSettings.dropzonejs.instances[selector.attr('id')].instance.destroy(), but after an AJAX rebuild the generated element ID changes, so the previous instance is not reliably destroyed.

Proposed resolution

Ensure that existing Dropzone instances are properly detached and destroyed before a widget is rebuilt during an AJAX form update.
The behavior should guarantee that only one Dropzone instance exists per widget element after Drupal.attachBehaviors() runs.

Possible approaches:
Implement a proper detach() handler for the DropzoneJS behavior and destroy existing instances when Drupal removes/replaces the widget during AJAX processing.
Or, before creating a new Dropzone instance, detect and destroy any existing Dropzone instance associated with the same widget/container element.
Ensure that drupalSettings.dropzonejs.instances remains synchronized with the current DOM element after AJAX rebuilds.

Issue fork dropzonejs-3611403

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

drupatz created an issue. See original summary.

velmir_taky made their first commit to this issue’s fork.

velmir_taky’s picture

Status: Active » Needs review

Added a detach handler to the dropzonejsIntegraion behavior. On unload it destroys the Dropzone instance of each widget being removed (resolved via Dropzone.forElement(), with a drupalSettings fallback), clears the stored drupalSettings.dropzonejs.instances[id].instance reference and releases the once() marker.

This ensures the old instance is torn down before the rebuilt element is re-attached, so a single instance remains after an AJAX rebuild instead of accumulating duplicates.

Also added DropzoneJsAjaxRebuildTest (functional JS) covering the scenario: it asserts one Dropzone.instances entry after an AJAX rebuild — red without the fix, green with it. Verified locally against Chrome via Selenium.

csakiistvan’s picture

Assigned: Unassigned » csakiistvan
csakiistvan’s picture

Assigned: csakiistvan » Unassigned
Status: Needs review » Reviewed & tested by the community
StatusFileSize
new362.16 KB
new279.81 KB

Environment

  • Drupal: 11.4.4
  • PHP: 8.5.5
  • Database: MariaDB 10.11.18
  • DDEV: 1.25.2
  • DropzoneJS: 8.x-2.11
  • Browser: Chrome

Prerequisites

  • DropzoneJS enabled, plus its test support module dropzonejs_test, which provides the AJAX rebuild test form at /dropzonejs-test/ajax-rebuild (added by MR !35).
  • A user with the access content and dropzone upload files permissions.
  • The reproduction relies on an AJAX-enabled form containing a DropzoneJS element that is rebuilt (its wrapper is replaced), so Drupal detaches behaviors on the old element and re-attaches on the new one.

Steps

  1. Apply the fix from MR !35: add a detach handler to the DropzoneJS behavior that destroys the existing Dropzone instance when the element is removed during an AJAX rebuild.
  2. Rebuild caches: ddev drush cr
  3. Visit /dropzonejs-test/ajax-rebuild.
  4. Upload a JPG or PNG file into the DropzoneJS element.
  5. In the browser console, check Dropzone.instances.length (expected: 1).
  6. Press the Rebuild button to trigger the AJAX form rebuild.
  7. In the browser console, check Dropzone.instances.length again.

Expected results

  • Exactly one Dropzone instance exists after the initial upload.
  • Exactly one Dropzone instance remains after the AJAX rebuild.
  • File removal continues to work on the rebuilt element.

Actual results

Before the fix, the DropzoneJS behavior had no detach handler, so after the AJAX rebuild the old instance was left registered while a new one was created on the rebuilt element (which has a different generated ID), leaving Dropzone.instances.length === 2 with broken file removal. After applying the fix, the detach handler destroys the previous instance on unload and clears its stale reference in drupalSettings, so a single instance remains (Dropzone.instances.length === 1) after the rebuild and file removal works as expected.


Testing produced with the assistance of an LLM.