[Bug] DX: due to destruction happening "at any time" / asynchronously, we cannot ergonomically use a conditional `on` modifier (aka conditional `on` modifiers are not possible if the condition goes from true to true))
🐞 Describe the Bug
Code
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import * as eModifier from 'ember-modifier';
const lifecycleTest = eModifier.modifier((element, [counter]) => {
console.log('setting up', counter);
return () => {
console.log('removing', counter);
}
})
class Demo extends Component {
@tracked count = 0;
get isEven() {
return this.count % 2 === 0;
}
double = () => {
console.log('eh?');
this.count = this.count * 2;
}
increment = () => this.count++;
<template>
<button
{{ (if this.isEven (modifier on 'click' this.double))}}
{{ (if this.isEven (modifier lifecycleTest this.count))}}
style="{{if this.isEven "opacity: 1;" "opacity: 0.5; cursor: not-allowed"}}"
>
Can only click me when even
</button>
<button {{on 'click' this.increment}}>
Can always click
</button>
<div>
count: {{this.count}}<br>
isEven: {{this.isEven}}
</div>
<br>
</template>
}
<template>
<Demo />
</template>
😕 Actual Behavior
teardown happens after setup, but because the same reference to the function that handles the 'click' event is passed, the event listener is removed.
when clicking the double button (can only click me when even),
- this.isEven is dirtied twice, even though the value remains the same
- when isEven is dirtied, the modifier destroys itself and re-sets itself up
🤔 Expected Behavior
Event listener is not removed.
🌍 Environment
- Ember: - 5.5
➕ Additional Context
As a work-around, or maybe ... the solution? we need to create a new reference to the function every time the condition changes so that each addEventListener / removeEventListener pair has a unique reference to the function to handle the event.
atm, I'm trying to figure out how to do that tho
here is a solve:
had to do a custom on modifier that always made its own local ref of the passed function:
const myOn = eModifier.modifier((element, [eventName, handler]) => {
let ownRef = (...args) => handler(...args);
element.addEventListener(eventName, ownRef);
return () => {
element.removeEventListener(eventName, ownRef);
}
})
Candidates in case this is regression:
- https://github.com/glimmerjs/glimmer-vm/commit/e3cd32de907870548bd7f450bd8cbfd81599e52c#diff-301b4fe274bd34f5552c35fc1f58078575896ba487df721bed939eb1d0fe9891
- https://github.com/glimmerjs/glimmer-vm/commit/64eb186f5a176aa9c70c48db1989fe2da21dbb25#diff-301b4fe274bd34f5552c35fc1f58078575896ba487df721bed939eb1d0fe9891R1
Gonna try to pair with @chancancode on some of this.
I'm getting hung up on @glimmer-workspaces made up testing environment :sweat_smile: