Components with same key remount after v-if toggle
Vue version
3.3.4
Link to minimal reproduction
Steps to reproduce
- Click toggle
- See that 'Loud setup() called' gets logged to console with every click.
- Delete comment
- Click toggle
- See that 'Loud setup() called' only gets logged once.
- See that Loud component doesn't update its dom despite the props value change.
What is expected?
Every toggle, Loud's prop value changes, so I expect that to be reflected in the dom. Further, since Loud's keys match, I expect to only see 'Loud setup() called' once.
What is actually happening?
If the comment is there, it remounts the component every time. If the comment is removed, it never updates the component when props changes.
System Info
System:
OS: macOS 13.3.1
CPU: (8) arm64 Apple M1 Pro
Memory: 256.58 MB / 16.00 GB
Shell: 5.2.15 - /opt/homebrew/bin/bash
Binaries:
Node: 18.14.2 - ~/opt/node-v18.14.2-darwin-arm64/bin/node
npm: 9.5.1 - ~/opt/node-v18.14.2-darwin-arm64/bin/npm
Browsers:
Chrome: 113.0.5672.126
Safari: 16.4
Any additional comments?
I have a component with a video in the dom that I would like to just move around via some layout config.
<template v-if="useLayout1">
<MyCompWithVideo key="one" ... />
<MyCompWithVideo key="two" ... />
</template>
<template v-else>
<MyCompWithVideo key="two" ... />
<MyCompWithVideo key="one" ... />
</template>
This is expected behavior in Vue 3 (which is intentionally different from Vue 2). v-if
branches always remounts because they are considered branches with different keys at the root. Keys on nested elements won't affect the behavior.
So is it bug if they are not unmounting when the comment is removed then?
<script setup>
const toggle = ref(true)
...
</script>
...
<template v-if="toggle">
<Loud key="one" val="hi" />
</template
<template v-else>
<Loud key="one" val="hey" />
</template>
It renders hi, but on toggle, never renders hey.
Vue tries to reuse nodes with the same key and tag under the same parent element as much as possible. However, since the template is not a real node, your v-if and v-else are under the same parent element. You can use different keys to render them or use different parent elements to render them.
<template v-if="onOffToggle">
<Loud key="one" :val="'hello'" />
</template>
<template v-else>
<Loud key="tow" :val="'hey'" />
</template>
or
<div v-if="onOffToggle">
<Loud key="one" :val="'hello'" />
</div>
<div v-else>
<Loud key="one" :val="'hey'" />
</div>