Subscribe on changes!

Hydration producting mashed up DOM after client side-change of array of dynamic components+data

avatar
Aug 24th 2023

Vue version

3.3.4

Link to minimal reproduction

https://stackblitz.com/edit/github-vxyebj-xwfogt?file=src%2FApp.vue

Steps to reproduce

consider this App.vue:

<script setup>
import { ref } from 'vue';
import Blue from './components/Blue.vue';
import Red from './components/Red.vue';
import Black from './components/Black.vue';
const content = ref([
  {
    key: 'server_1',
    component: Blue,
    text: 'i am blue',
  },
  {
    key: 'server_2',
    component: Red,
    text: 'i am red',
  },
  {
    key: 'server_3',
    component: Black,
    text: 'i am black',
  },
]);

if (typeof window !== 'undefined') {
  content.value = [
    {
      key: 'client_1',
      component: Blue,
      text: 'i am blue',
    },
    {
      key: 'client_2',
      component: Black,
      text: 'i am black',
    },
  ];
}
</script>

<template>
  <div>
    <component
      v-for="item in content"
      :key="item.key"
      :is="item.component"
      :text="item.text"
    />
  </div>
</template>

here, the content ref gets updated on the client with the middle (red) component missing).

What is expected?

I would expect to see the text i am blue wrapped in the Blue component (and thus written in blue color) and i am black wrapped in the Black component (and thus written in black color).

What is actually happening?

Instead i am black is displayed in red, seemingly wrapped in the Red component, which was removed. the text of any items after the removed component gets shoved into the previous compon

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 16.20.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.4.2 - /usr/local/bin/npm
    pnpm: 8.6.10 - /usr/local/bin/pnpm
  npmPackages:
    vue: ^3.3.4 => 3.3.4

Any additional comments?

No response

avatar
Aug 24th 2023

You should not use the index as the key, as this can lead to rendering errors when updating the array. You can use the following approach instead:

 <component
      v-for="item in content"
      :key="item.text"
      :is="item.component"
      :text="item.text"
    />

Of course, for this you need to make sure the key is unique.

avatar
Aug 24th 2023

@Alfred-Skyblue thanks for your suggestion! unfortunately it does not solve the problem. i've updated reproduction and issue-text using unique keys. the problem still persists.

avatar
Aug 31st 2023

The root cause is the list is changed during hydration, If the change only happens on the client side, here is a workaround:

onMounted(()=>{
  content.value = [
    {
      key: 'client_1',
      component: Blue,
      text: 'i am blue',
    },
    {
      key: 'client_2',
      component: Black,
      text: 'i am black',
    },
  ];
})
avatar
Aug 31st 2023

@edison1105 Yes, thanks. that's working.

Question is, if my example still is a bug, or if vue principally requires client-side changes to be performed within onMounted(). If the last one is the case, feel free to close this issue.

avatar
Aug 31st 2023

@codeflorist I feel this is not a bug, because we do not modify the data during hydration. /cc @LinusBorg what do you think?