mismatched child nodes count in `patchBlockChildren` when rendering component returning render function in JSX from `computed()` with `<slot v-if="ref">`
Vue version
3.4.18
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-cs8k7r?file=src%2FScopedSlots.vue%2Csrc%2FApp.vue
Steps to reproduce
Click the Toggle flag
button.
What is expected?
No exception was thrown.
What is actually happening?
TypeError: Cannot read properties of undefined (reading 'el')
at patchBlockChildren (chunk-EVMNRHZZ.js?v=f93e5a51:7163:18)
at processFragment (chunk-EVMNRHZZ.js?v=f93e5a51:7266:9)
at patch (chunk-EVMNRHZZ.js?v=f93e5a51:6758:9)
at ReactiveEffect.componentUpdateFn [as fn] (chunk-EVMNRHZZ.js?v=f93e5a51:7542:9)
at ReactiveEffect.run (chunk-EVMNRHZZ.js?v=f93e5a51:227:19)
at instance.update (chunk-EVMNRHZZ.js?v=f93e5a51:7586:17)
at callWithErrorHandling (chunk-EVMNRHZZ.js?v=f93e5a51:1667:32)
at flushJobs (chunk-EVMNRHZZ.js?v=f93e5a51:1874:9)
System Info
No response
Any additional comments?
By adding a breakpoint just before the exception was thrown: https://github.com/vuejs/core/blob/3f92126a26a544dfa69211cf2977556a2706bb2c/packages/runtime-core/src/renderer.ts#L968, we can see the length of two children
params is mismatched, oldChildren.length
will always be less one than the newChildren.length
:
The <FontAwesomeIcon>
is returning a render function h()
within computed()
to dynamically build elements by the component props:
https://github.com/FortAwesome/vue-fontawesome/blob/560b07aade4b8b1f909b448391968935ff9a759c/src/components/FontAwesomeIcon.js#L175
https://github.com/FortAwesome/vue-fontawesome/blob/560b07aade4b8b1f909b448391968935ff9a759c/index.js#L296
https://github.com/FortAwesome/vue-fontawesome/blob/560b07aade4b8b1f909b448391968935ff9a759c/index.js#L329-L337
The following approaches will prevent this exception:
- Remove
v-if="!flag"
from<slot>
- <slot v-if="!flag" :renderer="renderer" />
+ <slot :renderer="renderer" />
- Remove scoped slot prop
renderer
and render them in the fallback content of slot instead of the slot usage in parent component<App>
: https://stackblitz.com/edit/vitejs-vite-nckfwx?file=src%2FScopedSlots.vue%2Csrc%2FApp.vue
- <slot v-if="!flag" :renderer="renderer" />
+ <slot v-if="!flag">
+ <RenderFunction :renderer="renderer" />
+ </slot>
- <ScopedSlots>
- <template #default="{ renderer }">
- <span>
- <RenderFunction :renderer="renderer" />
- </span>
- </template>
- </ScopedSlots>
+ <ScopedSlots />
- Remove
computed()
fromrenderer
: https://stackblitz.com/edit/vitejs-vite-hgdag5?file=src%2FScopedSlots.vue%2Csrc%2FApp.vue
const flag = ref(true);
- const renderer = computed(() => (
+ const renderer = (
<>{flag.value === false && <FontAwesomeIcon icon="times" />}</>
- ));
+ );
leads to clicking the button will not re-create VNode
from renderer
, but if the initial value of flag
is true
its reactivity will somehow be resumed:
- const flag = ref(true);
+ const flag = ref(false);
- Replace the component
<FontAwesomeIcon>
that is based on a render function with any other component using plain<template>
or just someHTMLElement
- <>{flag.value === false && <FontAwesomeIcon icon="times" />}</>
+ <>{flag.value === false && <span>{JSON.stringify(flag.value)}</span>}</>
+ <>{flag.value === false && <div></div>}</>