`onBeforeUnmount` weird behaviour if the the custom component is on the same DOM tree level.
Vue version
3.3.8
Link to minimal reproduction
https://stackblitz.com/edit/vue3-script-setup-with-vite-l1j4xb?file=src%2FApp.vue
Steps to reproduce
This problem could me my missing knowledge how vnodes and lifecycle hooks working under the hood, but this behaviour is a little bit odd for me because it's working differently with a fresh vite create app and on stackblitz but works differently on SFC Playground.
This is the behaviour on my local env (also on stackblitz): https://github.com/mateenagy/vue-weird-rerender
- Create a new project with
npm create vite@latest my-vue-app -- --template vue-ts
- Create a simple component with some prop and add
onBeforeUnmount
hook with logging out the props.name for example. - Use the created component under each other like this:
<div>
<Test name="foo" />
<Test name="bar" />
<div>
- Open console in browser then delete the
name=foo
component - Browser console will log
bar
instead offoo
The behaviour in this is different because when I remove the first component then both onBeforeUnmount
event triggered.
What is expected?
I leave some commented out example where it's working as expected:
When I remove the name=foo
component then the onBeforeUnmount
should log foo
What is actually happening?
It's actually trigger the last component onBeforeUnmount
hook on the same DOM level:
<div>
<Test name="foo" /> <--- If I remove this
<Test name="bar" /> <--- This `onBeforeUnmount` will be triggered
</div>
This not happens in these cases:
<div>
<Test name="foo" /> <--- If I remove this then this component `onBeforeUnmount` hook will be trigged
<div></div>
<Test name="bar" />
</div>
<div>
<div>
<Test name="foo" /> <--- If I remove this then this component `onBeforeUnmount` hook will be trigged
</div>
<div>
<Test name="bar" />
</div>
</div>
<div>
<Test name="foo" v-if="show"/> <--- If I remove this then this component `onBeforeUnmount` hook will be trigged
<Test name="bar" />
</div>
System Info
No response
Any additional comments?
No response
I'm not sure if this is a bug, but it's all explainable.
In this use case, the compiler will think that it is a stable dom structure, so it will not attach any patchFlag. However, due to the deletion of the code, the dom structure is destroyed, and during the hot update process, the render function is re-executed, so it is wrong. The dom is updated, that is, foo
becomes bar
, and then uninstalled.
<Comp name="foo" key = 'foo'/>
<Comp name="bar" key = 'bar'/>
Similar to this use case, there is no correct binding key, causing the dom structure to be updated incorrectly when it is destroyed.
Yes it was on me to forget about the description on the :key
property on the component in the vue docs:
Without keys, Vue uses an algorithm that minimizes element movement and tries to patch/reuse elements of the same type in-place as much as possible.
And the it make perfect sense, but I just show a basic example (the stackblitz link contains the :key
workaround). My use case is that I have a component which value is in a global store and if I remove it then it updates the store. The problem is In this case that If I don't know the name from the component then the I cant remove from the store properly. The key is a workaround but not really a good developer experience and I mean when I am not using v-for.
I wonder if it's possible to have the option for render function to tell the renderer to not patch/reuse that elements instead just remove it? So I don't have to use key on every singe component when I use them under each other.
Something like this:
defineComponent({
patch: false,
props: {
name: {
type: String,
default: '',
},
default: {
type: [String, Boolean, Number, Object],
default: '',
},
ignore: {
type: Boolean,
default: false,
},
preserve: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
setup: (props, ctx) => {
return () => {
return h(component, {
...props,
name: props.name,
},
{ ...ctx.slots });
};
},
});