Subscribe on changes!

Rerender Async Component when it is root node of a Sync component before hydration crash the vue runtime.

avatar
Oct 25th 2022

Vue version

3.2.41

Link to minimal reproduction

https://github.com/mmis1000/vue-ssr-hydration-bug-report

Steps to reproduce

  1. Create an SSR app structured like this in vdom
<!-- App.vue -->
<Suspense>
  <Child>
</Suspense>
<!-- Child.vue -->
<AsyncWrapper :value="context.value" />
<BadSyncComponent />
<!-- AsyncWrapper.vue -->
<Async />
<!-- Async.vue -->
<template>
  <div><div/>
</template>
<script setup>
await new Promise(r => r())
</script>
<!-- Sync.vue -->
<template>
  <div><div/>
</template>
<script setup>
context.value = 2
</script>

The Sync.vue is a bad component that tries to change the value from context before rendering, which is already tracked by its sibling component.

This would cause the AsyncWrapper to be re-rendered even it is not hydrated yet.

  1. Start the app and open it in browser

What is expected?

The app reports hydration mismatch but otherwise functions normally.

What is actually happening?

The whole app crashed with following error.

Uncaught (in promise) TypeError: can't access property "shapeFlag", vnode is null
    getNextHostNode runtime-core.esm-bundler.js:6229
    getNextHostNode runtime-core.esm-bundler.js:6230
    componentUpdateFn runtime-core.esm-bundler.js:5699
    run reactivity.esm-bundler.js:187
    update runtime-core.esm-bundler.js:5729
    updateComponent runtime-core.esm-bundler.js:5554
    processComponent runtime-core.esm-bundler.js:5487
    patch runtime-core.esm-bundler.js:5085
    patchKeyedChildren runtime-core.esm-bundler.js:5849
    patchChildren runtime-core.esm-bundler.js:5792
    processFragment runtime-core.esm-bundler.js:5472
    patch runtime-core.esm-bundler.js:5078
    componentUpdateFn runtime-core.esm-bundler.js:5695
    run reactivity.esm-bundler.js:187
    update runtime-core.esm-bundler.js:5729
    callWithErrorHandling runtime-core.esm-bundler.js:155
    flushJobs runtime-core.esm-bundler.js:388
    promise callback*queueFlush runtime-core.esm-bundler.js:280
    queueJob runtime-core.esm-bundler.js:274
    effect runtime-core.esm-bundler.js:5727
    triggerEffect reactivity.esm-bundler.js:396
    triggerEffects reactivity.esm-bundler.js:386
    trigger reactivity.esm-bundler.js:354
    set2 reactivity.esm-bundler.js:525
    setup SyncInner.vue:9
    callWithErrorHandling runtime-core.esm-bundler.js:155
    setupStatefulComponent runtime-core.esm-bundler.js:7204
    setupComponent runtime-core.esm-bundler.js:7159
    mountComponent runtime-core.esm-bundler.js:5508
    hydrateNode runtime-core.esm-bundler.js:4714
    hydrateChildren runtime-core.esm-bundler.js:4853
    hydrateFragment runtime-core.esm-bundler.js:4879
    hydrateNode runtime-core.esm-bundler.js:4694
    hydrateSubTree runtime-core.esm-bundler.js:5594
    componentUpdateFn runtime-core.esm-bundler.js:5608
    run reactivity.esm-bundler.js:187
    update runtime-core.esm-bundler.js:5729
    setupRenderEffect runtime-core.esm-bundler.js:5743
    mountComponent runtime-core.esm-bundler.js:5525
    hydrateNode runtime-core.esm-bundler.js:4714
    hydrateChildren runtime-core.esm-bundler.js:4853
    hydrateElement runtime-core.esm-bundler.js:4815
    hydrateNode runtime-core.esm-bundler.js:4705
    hydrateSuspense runtime-core.esm-bundler.js:1533
    hydrateNode runtime-core.esm-bundler.js:4756
    hydrateChildren runtime-core.esm-bundler.js:4853
    hydrateElement runtime-core.esm-bundler.js:4815
    hydrateNode runtime-core.esm-bundler.js:4705
    hydrateSubTree runtime-core.esm-bundler.js:5594
    componentUpdateFn runtime-core.esm-bundler.js:5608
    run reactivity.esm-bundler.js:187
    update runtime-core.esm-bundler.js:5729
    setupRenderEffect runtime-core.esm-bundler.js:5743
    mountComponent runtime-core.esm-bundler.js:5525
    hydrateNode runtime-core.esm-bundler.js:4714
    hydrate2 runtime-core.esm-bundler.js:4608
    mount runtime-core.esm-bundler.js:4437
    mount runtime-dom.esm-bundler.js:1593
     entry-client.ts:6

System Info

System:
    OS: Windows 10 10.0.22621
    CPU: (32) x64 AMD Ryzen 9 5950X 16-Core Processor
    Memory: 31.10 GB / 63.91 GB
  Binaries:
    Node: 16.13.2 - C:\Program Files\nodejs\node.EXE
    npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.22621.674.0), Chromium (106.0.1370.52)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    vue: ^3.2.31 => 3.2.41

Any additional comments?

The bug is caused by conflict between https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L1231 and https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L1494

During hydration, the el exists but subtree is actually null.

And when you trigger update in this case. The second line above calls https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L2306 to retrieve the subtree for the mount point, which in turns didn't work and crash the whole app.

This bug happened to me when I am using vee-validate in the nuxt3 and it does this

https://github.com/logaretm/vee-validate/blob/8ccfd2b2b542963d3d35cfe5f82490c94ec1635f/packages/vee-validate/src/useForm.ts#L526

in its setup. The fieldsByPath.value is a context object shared by its consumer. Which may already has been rendered in the vdom but not hydrated yet like report above.

avatar
Nov 13th 2022

Related to #6095?

avatar
Nov 13th 2022

Related to #6095?

Seems to be exactly the path that cause error.

Although the prompt way of fix this issue seems questionable to me. (I don't have enough knowledge about suspense to say it is right or wrong with confident)

Is it actually actually okay to ignore a partially loaded component when update the tree?