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

Author: codeCraft-RitikCreated Sep 3, 2026Updated Sep 3, 2026

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:
    javascript
    this.$chips = $();
  2. When chips are rendered or added in _renderChips() (line 323) and addChip() (line 411):
    javascript
    // 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:
    javascript
    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:
    javascript
    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:

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