-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3.2+ Cleanup function is not called within specific component #613
Comments
👋 thanks for the reproduction! any chance the reproduction can be reduced to bare minimum? there is a lot going on in there, and some things looked not wired up correctly :-\ |
I think I have a bead on this, but it requires Ember 3.27+ for the My theory is that if you're specifying a modifier conditionally, and then remove the template from the DOM while the modifier is still present, the modifier's destructor function won't be called. {{!-- my-component.hbs --}}
<div
{{(if
@myFlag
(modifier "my-modifier")
)}}
>stuff</div> The syntax above is based on Chris Krycho's article on conditional modifiers
In my case I observed this in a rendering test. Forgive me, our tests are in mocha (actually, pray for me) it('never calls the destructor', async function () {
this.set('myFlag', false);
await render(hbs`
<MyComponent @myFlag={{this.myFlag}}/>
`);
this.set('myFlag', true);
// the functional modifier has run
await clearRender();
// the modifier's destructor has not run!
});
it('calls the destructor', async function () {
this.set('myFlag', false);
await render(hbs`
<MyComponent @myFlag={{this.myFlag}}/>
`);
this.set('myFlag', true);
// the functional modifier has run
this.set('myFlag', false);
// the modifier's destructor runs
await clearRender();
}); I see similar syntax used in brenner-company's example: floater=(if this.reference (
modifier
this.floaterModifier
this.reference
this.arrow
defaultPlacement=@defaultPlacement
options=@options
destroy=this.destroy
)) I'm not actually sure whose bug this is! ember-modifier? glimmer-component? ember-source? some handlebars template lib? |
This is super helpful, @apellerano-pw. I had a bit of a tangent while trying to reproduce though, for example, <div {{ (if @enabled (modifier testModifier))}}>stuff</div> {{! strict mode }}
On stackblitz, I'm not using strict mode, so was able to make progress -- I found that this works as you would expect it to (destructing every time the if statement becomes false). <fieldset><legend>inline</legend>
<div {{ (if @enabled (modifier 'my-modifier' "inline")) }}>content</div>
</fieldset> So now, what's left is to conditionally yield like in your floater example: <fieldset><legend>yielded</legend>
<YieldModifier @condition={{@enabled}} as |myModifier|>
<div {{myModifier}}>content</div>
</YieldModifier>
</fieldset> But I was still unable to reproduce the behavior you describe.
Do you have an open source repo that I could run with minimal examples (as in my stackblitz, or in your test snippets?) -- I'd like to be able to run something "known to be goofy", so that maybe I don't miss some detail when I create my own reproduction. Thanks!!! |
@NullVoxPopuli I wasn't able to run your stackblitz, but I made a small repro repo. I believe the bug has to do specifically with removing (conditionally applied) modifiers from the DOM; the behavior works fine when setting the conditional true/false and the modifier remains in the DOM. It might be possible to trigger in your stackblitz if you did something like this: {{#if this.enabled}}
<TestCondition @enabled={{this.enabled}}/>
{{/if}}
{{!--
It would depend on how Ember propagates state changes.
Does the `(if)` helper destroy `<TestCondition>`, and never pass the new value down to it?
Or does the component update before it's destroyed by the `(if)` change?
--}} For my repro, I use the test environment. The modifier adds a class to P.S. My repro is Ember 3.28 and ember-modifier 2.x since that is the exact environment I noticed the issue in. |
I'm using firefox, and my blitz works, but yours does not 🤔 One thing I noticed is that you're using:
After upgrading these packages to:
And I was able to boot your project here: https://stackblitz.com/edit/github-eh3tjp?file=package.json I see that your first test fails: adding / removing a class isn't a great indicator of the described behavior because destruction is async (extra complexity to test for when modifying classList, and if the modifier is re-enabled, the destructor could remove the class added in the next modifier run). I was able to reproduce your test with something similar, yet not messing with the element: test('assert calls the destructor', async function (assert) {
this.set('enabled', false);
this.set(
'myModifier',
modifier((element) => {
assert.step('myModifier set up');
return () => assert.step('myModifier cleaned up');
})
);
await render(hbs`
<div {{(if this.enabled (modifier this.myModifier))}}>
qwerty
</div>
`);
assert.verifySteps([]);
this.set('enabled', true);
await settled();
await clearRender();
assert.verifySteps(['myModifier set up', 'myModifier cleaned up']);
}); I can confirm the test passes if I change the await render(hbs`
<div {{this.myModifier}}>
qwerty
</div>
`); so this at least means we have a legit reproduction!!! 🎉 I have a hunch where the fix would need to go, but I'm not certain. |
Just commenting here to say I've hit this issue today, exactly as described. I essentially had code like this, where I was using the {{#if this.selectedPreview}}
<Modal>
// loading spinner etc.
// currentManifest is a trackedFunction
<div
{{(if this.selectedPreview.currentManifest.value
(modifier myModifier this.selectedPreview.currentManifest.value)
)}}
/>
</Modal>
{{/if}}
Funnily enough I was following the same guide 😄 Glad to see there's a repro in this PR - awesome detective work!! If anyone needs a workaround, this worked for me; {{#if this.selectedPreview.currentManifest.value}}
<div {{myModifier this.selectedPreview.currentManifest.value}} />
{{else}}
<div />
{{/if}} |
For a package I'm working on I'm creating a floating-ui modifier & component (using https://floating-ui.com/) to attach floating elements to a trigger/reference. Everything seems to work fine, but I've encountered an issue today: as documented within
ember-modifier
, you need to return a cleanup function to remove any added event listeners, etc when the component gets unloaded.When I test this with just the modifier, everything works as expected: the cleanup function gets called when eg. navigation away from a page with said modifier. But when I use the component version I've created, the cleanup function is not being called.
I can easily check this by creating the following setup: two toggles that add & remove the modifier and component version. When I open the event listener inspector in Chrome and click the toggle a few times I see the following results.
Modifier version:
Component version:
(The event listeners are not getting removed here)
I've put up an example of that code at: https://codesandbox.io/s/cool-morning-9moikc?file=/app/modifiers/au-floating-ui.js (component version can be found under
app/components/au-floating-ui.hbs
&app/components/au-floating-ui.js
)Could the specific structure of the component be an issue here or have I stumbled upon a bug?
By the way: my code is pretty similar (regarding structure) to https://github.com/CrowdStrike/ember-velcro but with less customisation. So, the same issue would also be present in there.
The text was updated successfully, but these errors were encountered: