haunted icon indicating copy to clipboard operation
haunted copied to clipboard

Virtual component never unmounts

Open lucaseverett opened this issue 6 years ago • 21 comments

I've found an issue when using virtual components. The teardown function returned from useEffect is never invoked. Also, state remains after a virtual component is unmounted.

The following sandbox uses the same function as both a real component and a virtual component.

When showing/hiding the real component, the count is reset, as expected. However, when showing/hiding the virtual component, the count remains the same.

Also, as you can see from console.log, the virtual counter's useEffect teardown is never invoked.

https://codesandbox.io/s/mzvrmj2z99

I noticed issue #42, but I don't know if this is related, so I created a new issue.

lucaseverett avatar Feb 26 '19 05:02 lucaseverett

Oh, so that was it. I was having problems to integrate haunted with my router and showing a loader between page navigations. The loader didn't disappear on a second time.

nicolasparada avatar Feb 28 '19 19:02 nicolasparada

@nicolasparada Just out of curiosity, why are you using virtual components? What's the appeal to you? Again, just curious because I don't use them personally :)

Thanks for the sample @lucaseverett! I'll take a look at this tomorrow. Can't commit on a fix but having a sample makes it 1000x easier!

matthewp avatar Feb 28 '19 23:02 matthewp

Not much. I'm not calling these components with the tag name but with the function. I don't need the connected/disconnectedCallback if I'm using useEffect. And I still cannot get around shadow DOM. I like my CSS to cascade.

Virtual components are more simple 🙂

nicolasparada avatar Feb 28 '19 23:02 nicolasparada

I'm mean... I'm doing all in JavaScript so is no much difference doing:

html`
  <some-component .prop=${value}></some-component>
`

Than:

html`
  ${SomeComponent({ prop: value })}
`

nicolasparada avatar Feb 28 '19 23:02 nicolasparada

I don't need the tag name is what I'm trying to say.

nicolasparada avatar Feb 28 '19 23:02 nicolasparada

@nicolasparada Would you be more included to use components if you didn't have to define a tag name?

import { define, component } from 'haunted';

define(component(() => html` ... `))

Where define is a new thing that doesn't exist yet :)

matthewp avatar Mar 01 '19 00:03 matthewp

Not really. withHooks is all I need 🙂

nicolasparada avatar Mar 01 '19 01:03 nicolasparada

I think I have a fix for this. Working on test. Might be tomorrow before its in.

matthewp avatar Mar 01 '19 01:03 matthewp

Fix is in #70 which I plan to release tomorrow morning if there are no objections.

matthewp avatar Mar 01 '19 02:03 matthewp

Fixed in 4.1.4

matthewp avatar Mar 01 '19 12:03 matthewp

I have a very funny bug. The thing is... it doesn't work when I try it locally, but it works when I upload it to netlify. github.com/nicolasparada/reprod-haunted-routing

nicolasparada avatar Mar 01 '19 18:03 nicolasparada

Yet it works perfectly fine when using component().

nicolasparada avatar Mar 01 '19 18:03 nicolasparada

Ok, let's reopen, i'll try to take a look at that one too.

matthewp avatar Mar 01 '19 19:03 matthewp

I'm still seeing an issue with my original example, if the Main component is used as a virtual component with lit-html render instead of mounted as a real component.

https://codesandbox.io/s/23m15p5rwy

The issue I'm seeing is that the teardown effect isn't ran on the first unmount of the virtual component, but subsequent mount/unmount works as expected.

The state is being reset, but if you watch the console you will see that the actual teardown effect isn't being invoked.

lucaseverett avatar Mar 07 '19 14:03 lucaseverett

There is also something weird happening if the same virtual component is used multiple times.

https://codesandbox.io/s/1qkzn946m7

Try increasing both counts and then hide the first one. The second one resets as well.

lucaseverett avatar Mar 07 '19 14:03 lucaseverett

By the way, Andrea is having some similar issues with Neverland.

https://github.com/WebReflection/neverland/issues/20

lucaseverett avatar Mar 07 '19 15:03 lucaseverett

Been on vacation, will try to take a look in the next few days.

matthewp avatar Mar 11 '19 12:03 matthewp

Hey, I have a fork with tests that demonstrate these issues and more.

I have found in @lucaseverett's 2 counter example that if you

  1. hide the first counter
  2. increment the second counter
  3. hide the second counter
  4. unhide the second counter

It will show 1 instead of the expected 0. I made a test for this but it fails early because of the other issue with the second counter being reset on the first counter's hide.

I also found that unmount calls never fire if there is any form of indirection. For example if you mount a template with a virtual component in it and then remove that template, the virtual component's unmount function is never called.

I have not thought too much about how to solve this but I wanted to get some tests that demonstrate these bugs.

asafigan avatar Apr 20 '19 15:04 asafigan

@matthewp, do you think you'll be able to resolve these issues? A sound virtual component w/ hooks feels out of reach to me.

jpray avatar Apr 20 '19 17:04 jpray

I just spent a long time ~~pointing~~ poking around with the virtual implementation. I am not convinced that an effect hook is possible for virtual components in lit-html. I can't figure out how to get a reference to the actual dom node for a virtual component.

When looking at what part gives you, it's not helpful. part.startNode is the first child of the node that the component will be inserted into. You can traverse up the tree to the root node of this piece of dom and add a mutation listener to it and tell the observer to observe that node and the whole subtree. The root turns out to be a temporary template unless the component is being mounted directly with render. The only mutation observed is all the current nodes being removed from the temporary template.

Therefore you never get a reference to the dom being rendered by the component or the actual host node of the finally rendered html.

There might be some weird work around you could do like observing the nodes being removed and then wait till they have been inserted into the final html somehow. Then observe their new parent and try to figure out which node is the result of your component. Than trigger clean up when the node is removed. But that seems super convoluted and extremely fragile.

If that wasn't hard enough your component can be unmounted indirectly like when it's parent is removed from the dom. Meaning you would have to observe all dom removals and traverse down every tree being removed and check if your node is a part of the tree. This would be a huge amount of overhead with every removal of dom and I think that it would erase any performance advantage of lit-html.

We might have to wait until lit-html adds apis to allow us to access the actual dom being rendered by a template. And then it still might not be a good idea.

asafigan avatar Apr 20 '19 20:04 asafigan

Thanks for the research @asafigan!

matthewp avatar Apr 22 '19 10:04 matthewp