Skip to content

TypeError in selectChip() during keyboard navigation and failure of destroy() due to immutable Cash .add() #6706

Description

@codeCraft-Ritik

Describe the Bug

In the Chips component (js/chips.js), keyboard navigation (pressing Arrow keys or Backspace on an empty input) causes an uncaught runtime error:
TypeError: Cannot read property 'focus' of undefined (or Cannot read properties of undefined (reading 'focus')).

Additionally, calling .destroy() on a Chips instance fails to remove the rendered chips from the DOM.

Root Cause Analysis

In Cash (and jQuery), the .add() method is immutable—it does not mutate the existing collection in-place; instead, it returns a new collection instance.

  1. In js/chips.js:49, this.$chips is initialized to an empty Cash collection:
    this.$chips = $();
  2. When chips are rendered or added in _renderChips() (line 323) and addChip() (line 411):
    // Line 323:
    this.$chips.add(chipEl); // Return value is discarded!
    
    // Line 411:
    this.$chips.add(renderedChip); // Return value is discarded!
  3. Because the returned collections are never assigned back to this.$chips, this.$chips remains an empty collection (length === 0).
  4. When selectChip(chipIndex) is triggered during keyboard navigation:
    selectChip(chipIndex) {
      let $chip = this.$chips.eq(chipIndex); // Empty collection
      this._selectedChip = $chip;
      $chip[0].focus(); // CRASH: $chip[0] is undefined
    }
  5. In destroy(), this.$chips.remove() does nothing because this.$chips.length === 0, leaving orphaned elements in the DOM.

Steps to Reproduce

  1. Initialize a Chips component with initial data:
    const elem = document.querySelector('.chips');
    const instance = M.Chips.init(elem, {
      data: [{ tag: 'Apple' }, { tag: 'Microsoft' }, { tag: 'Google' }]
    });
  2. Focus the input field and press Backspace or Left Arrow key (or call instance.selectChip(0) programmatically).
  3. Check the browser console.

Expected Behavior

  • The selected chip receives focus and enables chip keyboard navigation.
  • Calling instance.destroy() cleans up and removes all generated chip DOM elements.

Actual Behavior

  • Uncaught TypeError: Cannot read property 'focus' of undefined.
  • instance.destroy() leaves all chip elements rendered in the DOM.

Proposed Fix

Assign the return value of .add() back to this.$chips:

--- a/js/chips.js
+++ b/js/chips.js
@@ -320,7 +320,7 @@
       this.$chips.remove();
       for (let i = 0; i < this.chipsData.length; i++) {
         let chipEl = this._renderChip(this.chipsData[i]);
         this.$el.append(chipEl);
-        this.$chips.add(chipEl);
+        this.$chips = this.$chips.add(chipEl);
       }
 
       // move input to end
@@ -408,7 +408,7 @@
       }
 
       let renderedChip = this._renderChip(chip);
-      this.$chips.add(renderedChip);
+      this.$chips = this.$chips.add(renderedChip);
       this.chipsData.push(chip);
       $(this.$input).before(renderedChip);
       this._setPlaceholder();

Additional Bonus Findings

  1. js/dropdown.js (Line 389) — Uncaught TypeError in _getDropdownPosition():
    Inside _getDropdownPosition(), this.el.offsetParent.getBoundingClientRect() is executed. When the dropdown trigger is placed inside an element with position: fixed (such as a fixed navbar .navbar-fixed), this.el.offsetParent is null per W3C specification, causing an uncaught TypeError on open. Furthermore, offsetParentBRect is an unused variable.

  2. js/carousel.js (Lines 527–530) — Invalid NaNpx CSS Transform:
    In _scroll(), variable i is declared but uninitialized (undefined). Inside the first center item calculation block, translateX(${dir * this.options.shift * tween * i}px) evaluates to NaN, setting translateX(NaNpx) and causing the browser to reject the CSS transform.

  3. js/characterCounter.js (Line 54) — Instance Reference Leak in destroy():
    destroy() executes this.el.CharacterCounter = undefined; (missing the M_ prefix), leaving this.el.M_CharacterCounter intact and causing M.CharacterCounter.getInstance(el) to return the destroyed instance.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions