TypeError in selectChip() during keyboard navigation and failure of destroy() due to immutable Cash .add()
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.
- In
js/chips.js:49,this.$chipsis initialized to an empty Cash collection:this.$chips = $(); - When chips are rendered or added in
_renderChips()(line 323) andaddChip()(line 411):// Line 323: this.$chips.add(chipEl); // Return value is discarded! // Line 411: this.$chips.add(renderedChip); // Return value is discarded! - Because the returned collections are never assigned back to
this.$chips,this.$chipsremains an empty collection (length === 0). - 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 } - In
destroy(),this.$chips.remove()does nothing becausethis.$chips.length === 0, leaving orphaned elements in the DOM.
Steps to Reproduce
- 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' }] }); - Focus the input field and press
BackspaceorLeft Arrowkey (or callinstance.selectChip(0)programmatically). - 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
js/dropdown.js(Line 389) — UncaughtTypeErrorin_getDropdownPosition(): Inside_getDropdownPosition(),this.el.offsetParent.getBoundingClientRect()is executed. When the dropdown trigger is placed inside an element withposition: fixed(such as a fixed navbar.navbar-fixed),this.el.offsetParentisnullper W3C specification, causing an uncaughtTypeErroron open. Furthermore,offsetParentBRectis an unused variable.js/carousel.js(Lines 527–530) — InvalidNaNpxCSS Transform: In_scroll(), variableiis declared but uninitialized (undefined). Inside the first center item calculation block,translateX(${dir * this.options.shift * tween * i}px)evaluates toNaN, settingtranslateX(NaNpx)and causing the browser to reject the CSS transform.js/characterCounter.js(Line 54) — Instance Reference Leak indestroy():destroy()executesthis.el.CharacterCounter = undefined;(missing theM_prefix), leavingthis.el.M_CharacterCounterintact and causingM.CharacterCounter.getInstance(el)to return the destroyed instance.
Source: Dogfalo/materialize