core icon indicating copy to clipboard operation
core copied to clipboard

TransitionGroup fails to hydrate when child has v-if

Open jonaskuske opened this issue 3 years ago • 12 comments

Vue version

3.2.39

Link to minimal reproduction

https://sfc.vuejs.org/#__SSR__eNp9j81uwyAQhF8F7TmAmpxq0Sg99QV65ELiTUNjYLVgV1Xkdy9urCg/Um4wM7vf7AneidTQIzRgCgbqXMG1jUKYT3Yx++JT/ODU01ls/SCO+PtmwVkQg/T7+ty7LqOF9e7gu1a8GF1T5zjN4e3FXRo9r9KPAKMvFWABPlDiIoMj9Z1TrA1P06CdjWyhEf/KpNUTpr+FQymUG637SMcvtUtBb6qnuY/FB5RtCpuVWqrVa22Zy7WuMAe55fSTkSvRwuJqua7igCwZY4uM/BR2l70B3nkP0Ik52jjC+AeJqoxU

Steps to reproduce

No steps necessary, check the rendered output and the warning

What is expected?

The children inside <TransitionGroup> are rendered just once and the hydration succeeds

What is actually happening?

The child in <TransitionGroup> is rendered twice and a hydration warning appears

System Info

No response

Any additional comments?

I suppose that happens because <TransitionGroup> skips comment nodes (they can't be transitioned and have no boundingClientRect), so it expects the actual child but finds a comment node, "fixes" that by adding the expected child node even though it's already there, just one position behind

jonaskuske avatar Sep 21 '22 20:09 jonaskuske

I found that this is because the SSR side allows comment nodes in the TransitionGroup, while on the client side it skips the rendering of comment nodes, so it causes a mismatch warning during hydration.

Here is the source code snippet that skips the rendering of comment nodes:

https://github.com/vuejs/core/blob/5381abc0571e58a9be6cf482dc50c8db8300f86c/packages/runtime-core/src/components/BaseTransition.ts#L502-L505

zhangzhonghe avatar Sep 22 '22 12:09 zhangzhonghe

Yup! Looking into it atm.

I think one approach would be to pass true for the keepComment arg, another to change the generated SSR code so it doesn't output the comment HTML anymore (_push('<!---->')).

I suspect the first option causes issues because then the comment node is passed to setTransitionHooks(), but maybe the second works? Otherwise the TransitionGroup might need some additional checks, let's see.

jonaskuske avatar Sep 22 '22 12:09 jonaskuske

Is there any workaround available for this issue?

LiamMartens avatar Feb 28 '23 20:02 LiamMartens

For me it not only happens with v-if child, but also when you have a slot in a reusable transition component (as mentioned in this closed issue https://github.com/vuejs/core/issues/6922):

https://sfc.vuejs.org/#__SSR__eNp9kctqwzAQRX9FaJNNLEFbKBg1pHTRH+hSGz+mjRrrwUh2KcH/3pGTJk4M2c3cOeiO7hz4awhi6IGXXMUGTUgsQurDRjtjg8fE3rwN7BO9ZSshc5PxlXZKHnkiqUlgQ1cloI4xlbGporo1A9vD74vmleabZme6llVKkrwk6jNRXwg1uVKp5NmFr/lxvcJWQXxH7+gDh0zr0yBqXrJJyRqtnHvNdymFWErZu7D/Eo23ckszib1LxkLRert9FA/i6ZnsY5rrAqItavQ/EZAcNV/PHpckDoAFgmsBAe+a3bBXhjezhWn2HLUbKYD/W+TTXcf/gZWLJhnv3tFPt5xyjp1PTJ4yXTLzeMc/Dtm2iA==

maprox avatar Mar 09 '23 11:03 maprox

@LiamMartens

Is there any workaround available for this issue?

Replacing the v-if with a v-for should work:


<!-- error -->
<p v-if="shouldShow">Hello!</p>
<!-- works -->
<p v-for="_ in shouldShow ? [1] : []">Hello!</p>

jonaskuske avatar Mar 09 '23 18:03 jonaskuske

I've just run into the same issue using Nuxt.

The workaround I found was simply providing a v-else condition to render a self-closing empty element in case the v-if condition returns falsy which seems to solve the comment problem

<p v-if="computedProperty">{{ computedProperty }}</p>
<div v-else />

barksploit avatar Mar 23 '23 16:03 barksploit

This works for me :

<component :is="component" tag="div">
....
....
</component>
const mounted = ref(false);
const component = computed(() => (mounted.value ? TransitionGroup : 'div'));
onMounted(() => {
    mounted.value = true;
});

MehranTaheri avatar Jul 25 '23 07:07 MehranTaheri

It's been a year, Can we consider this now? @sodatea

jd-solanki avatar Aug 17 '23 10:08 jd-solanki

This worked for me too, had to make the element hidden so it doesn't mess with my styling,

<div class="hidden" v-else />

kyng-cytro avatar Sep 15 '23 11:09 kyng-cytro

~I think this is also happening when using slots that are empty. On the server nothing is rendered, but on the client and empty comment is rendered.~ edit: this seems to only happen in nuxt, and not in vue with vite. I will open an issue there.

SebbeJohansson avatar Sep 20 '23 14:09 SebbeJohansson

Hey, I just experienced the same issue. I only had a few child elements in the TransitionGroup, so I decided to use v-show directive instead of v-if. This seems to have resolved the HM issue in my case and kept the intended animation. Hope this helps!

detheuss avatar Jan 11 '24 14:01 detheuss

For me it not only happens with v-if child, but also when you have a slot in a reusable transition component (as mentioned in this closed issue #6922):

sfc.vuejs.org/#__SSR__eNp9kctqwzAQRX9FaJNNLEFbKBg1pHTRH+hSGz+mjRrrwUh2KcH/3pGTJk4M2c3cOeiO7hz4awhi6IGXXMUGTUgsQurDRjtjg8fE3rwN7BO9ZSshc5PxlXZKHnkiqUlgQ1cloI4xlbGporo1A9vD74vmleabZme6llVKkrwk6jNRXwg1uVKp5NmFr/lxvcJWQXxH7+gDh0zr0yBqXrJJyRqtnHvNdymFWErZu7D/Eo23ckszib1LxkLRert9FA/i6ZnsY5rrAqItavQ/EZAcNV/PHpckDoAFgmsBAe+a3bBXhjezhWn2HLUbKYD/W+TTXcf/gZWLJhnv3tFPt5xyjp1PTJ4yXTLzeMc/Dtm2iA==

This doesn't reproduce (anymore?). But v-if causes hydration issues even in the newest release of Vue. I've rebased my PR to get rid of the conflicts

jonaskuske avatar Mar 01 '24 14:03 jonaskuske