Skip to content

fix: render custom element arrays as a Fragment to avoid remounting on re-render - #531

Merged
fago merged 2 commits into
2.xfrom
fix/renderCustomElements-fresh-vnodes
Aug 19, 2026
Merged

fix: render custom element arrays as a Fragment to avoid remounting on re-render#531
fago merged 2 commits into
2.xfrom
fix/renderCustomElements-fresh-vnodes

Conversation

@drubot

@drubot drubot commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Closes #530.

Problem

renderCustomElements() is documented for <component :is="renderCustomElements(page.content)" />, which Vue re-evaluates on every render of the consuming component. For an array payload it returned a freshly created anonymous component on each call, so <component :is> saw a new component type every render and unmounted then remounted the entire custom-element subtree on any unrelated re-render — discarding component state, focus and scroll position. Single-element payloads (returned as a plain VNode) were unaffected, which makes the bug easy to miss.

Fix

Wrap the VNode[] in a Fragment instead of a new defineComponent:

if (Array.isArray(vnodes)) {
  return h(Fragment, vnodes)
}

A Fragment has a stable type, so a re-render patches the children in place rather than remounting, while still reflecting content changes. <component :is> accepts a VNode, so the documented template usage is unchanged and the rendered markup is identical (a Fragment emits its children with no wrapper element). The return type narrows from VNode | Component | null to VNode | null.

Test

Adds a regression test to test/nuxt/renderCustomElements.test.ts that renders an array payload the documented way, bumps an unrelated ref to force a parent re-render, and asserts a mount-counting child is not remounted. It fails on the old code (mount count 2) and passes with this fix (mount count 1). The existing array-rendering assertions (markup output, empty arrays) are unchanged.

Notes

Drafted with assistance from Claude Code.

loki et fago added 2 commits August 19, 2026 14:33
…n re-render.

renderCustomElements() is documented for `<component :is="renderCustomElements(page.content)" />`,
which re-invokes it on every render of the consuming component. For an array
payload it returned a freshly created anonymous component on each call, so
`<component :is>` saw a new component type every render and unmounted then
remounted the entire custom-element subtree on any unrelated re-render —
discarding component state, focus and scroll position (single-element payloads,
returned as a plain VNode, were unaffected).

Return the VNode[] wrapped in a Fragment instead. A Fragment has a stable type,
so a re-render patches the children in place rather than remounting, while still
reflecting content changes. `<component :is>` accepts a VNode, so the template
usage is unchanged.
@fago

fago commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

I verified the test fails with the fix, good improvement.

@fago
fago merged commit f39c2cb into 2.x Aug 19, 2026
1 check passed
@fago
fago deleted the fix/renderCustomElements-fresh-vnodes branch August 19, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

renderCustomElements() remounts the whole subtree on every re-render for array payloads

2 participants