Magento 2

Magento 2 Checkout Glitch: Unpacking the UI Modal TypeError and Its Fix

In the dynamic and often intricate world of Magento 2 development, even seemingly minor frontend glitches can significantly impact user experience, developer productivity, and overall platform stability. For e-commerce businesses leveraging Adobe Commerce or Magento Open Source, a smooth, error-free checkout process is non-negotiable. A recent GitHub issue, #41288, brought to light a persistent TypeError within Magento's UI modal widget, particularly affecting the critical checkout flow. As experts in Magento migration and optimization at Shopping Mover, we understand the importance of addressing such nuances to ensure a robust and reliable e-commerce platform.

Diagram showing Magento 2 modal double-close race condition leading to TypeError.
Diagram showing Magento 2 modal double-close race condition leading to TypeError.

The Persistent Frontend Glitch: A Deep Dive into Magento 2 UI Modals

The core of this issue lies within the _destroyOverlay() method of Magento's Magento\\Ui/js/modal/modal.js widget. This method is crucial for gracefully cleaning up the modal's overlay element once it's no longer needed. However, under specific, reproducible conditions, this method attempts to dereference an overlay that has either already been removed or was never properly initialized. This leads to an uncaught TypeError, manifesting as either Cannot read properties of null (reading 'remove') or Cannot read properties of undefined (reading 'remove') in the browser console.

Scenario 1: The "Double-Close" Race Condition (null variant)

This variant is a classic example of a race condition. It typically occurs when a user interacts with a modal in rapid succession, such as double-clicking the "Cancel" button or the "×" icon on a checkout address popup. Here's the breakdown:

  • The closeModal() method, responsible for initiating the modal's closure, queues a _close() handler on the transitionEvent for every call. It doesn't check if the modal is already in the process of closing.
  • If two `closeModal()` calls happen within the modal's closing animation window (typically around 300ms), both queued handlers will eventually fire on the same transition event.
  • The first handler successfully executes _close(), which calls _destroyOverlay(). This removes the overlay element from the DOM and sets this.overlay = null.
  • Crucially, the second handler then fires. When its `_close()` method calls `_destroyOverlay()`, it attempts to access `this.overlay.remove()`. Since `this.overlay` is now `null`, an uncaught TypeError: Cannot read properties of null (reading 'remove') is thrown.

While the order might still complete, this error creates persistent noise in front-end error monitoring systems and can be a red flag for developers.

Scenario 2: Closing a Never-Opened Modal (undefined variant)

The second scenario highlights a different edge case. It involves attempts to close a modal that was never actually opened or fully initialized. A prime example is found in Magento_Checkout/js/model/sidebar:

  • In the checkout process, specifically within shipping-information.js, the back() or backToShippingMethod() functions unconditionally call sidebarModel.hide().
  • However, the corresponding show() method for this particular order-summary modal is only ever reached from the mobile order-summary toggle. On desktop viewports, the modal might never have been fully opened, meaning its `overlay` property remains `undefined`.
  • When `sidebarModel.hide()` is called, it attempts to queue a `transitionend` handler. If a custom theme or extension introduces a CSS transition within that `aside` element (stock Luma typically has none in this subtree), the `transitionend` event will eventually fire.
  • The queued handler then calls `_close()` which, in turn, calls `_destroyOverlay()`. Since `this.overlay` is `undefined`, attempting to access `this.overlay.remove()` results in a TypeError: Cannot read properties of undefined (reading 'remove').

This scenario, though less common on stock Luma, becomes a real defect for any third-party `closed` subscriber, as the throwing `_close()` aborts before `_unsetActive()` and the `closed` trigger, potentially breaking custom logic.

The Elegant Solution: Guarding the Teardown

The fix, implemented in the associated pull request, is a testament to robust, defensive programming. It introduces a simple yet effective guard within the _destroyOverlay() method:

_destroyOverlay: function () {
    if (this.overlay) { // The crucial guard
        this.overlay.off(this.options.transitionEvent, this._removeOverlay);
        this.overlay.remove();
        this.overlay = null;
    }
    this.parentModalClass.remove();
},

This guard ensures that the teardown logic (detaching event handlers and removing the overlay from the DOM) only executes if this.overlay actually exists. This approach is consistent with existing Magento 2 code, such as `_unsetActive()` (the very next call inside `_close()`) which already uses `if (this.overlay) {`, and even the bundled jQuery UI dialog widget's own `_destroyOverlay()` method.

By returning early when `this.overlay` is `null` or `undefined`, the fix prevents the `TypeError` in both scenarios. In the double-close case, the first teardown already removed `parentModalClass`, and in the never-opened case, it was never added. The end state remains correct: no `.modals-overlay` elements, no `_has-modal` body class, and `body` overflow `visible`.

Why This Matters for Your Magento 2 Store and Migrations

For any business running on Adobe Commerce or Magento Open Source, understanding and addressing such core platform issues is vital:

  • Enhanced User Experience: A cleaner console means fewer perceived errors, contributing to a smoother, more trustworthy checkout process. Users are less likely to abandon carts if they don't encounter visual glitches or unresponsive elements, even if the underlying order completes.
  • Improved Developer Productivity: Eliminating persistent TypeError noise in front-end error monitoring tools allows developers to focus on genuine issues, reducing debugging time and false positives. This is particularly valuable for agencies and in-house teams managing complex Magento 2 environments.
  • Greater Stability for Extensions and Themes: Custom themes and third-party extensions often introduce their own animations or rely on modal `closed` events. This fix prevents core Magento errors from breaking such custom logic, ensuring better compatibility and reducing the risk of unexpected behavior.
  • Critical for Magento 2 Migrations and Upgrades: When undertaking a Magento 1 to Magento 2 migration, or even a significant Magento 2 upgrade, ensuring the stability of the core platform is paramount. At Shopping Mover, we emphasize thorough testing and code review to identify and mitigate such issues, guaranteeing that your new or upgraded Magento 2 store operates flawlessly from day one. A stable foundation prevents costly post-migration fixes and ensures a seamless transition for your business.

Conclusion: The Power of Meticulous Development

This seemingly small fix for a `TypeError` in Magento 2's UI modals underscores the importance of meticulous development and continuous improvement within the platform. For store owners, it translates to a more reliable checkout and a better user experience. For developers, it means cleaner code, fewer debugging headaches, and a more robust foundation for building custom functionalities. Staying updated with Magento's latest patches and partnering with experienced Magento migration experts like Shopping Mover ensures that your e-commerce platform remains performant, stable, and ready to meet the demands of your growing business.

Share:

Start with the tools

Explore migration tools

See options, compare methods, and pick the path that fits your store.

Explore migration tools