Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(hydration): pass namespace when patching dynamic props
When force-patching dynamic props during hydration (introduced in #9083),
the element namespace is not forwarded to `patchProp`, so it defaults to
`undefined`. As a result `shouldSetAsProp` treats SVG (and MathML) elements
as HTML and patches read-only props such as `width`, `height` and `viewBox`
as DOM properties, which throws (e.g. "Cannot set property viewBox of
SVGSVGElement which has only a getter").

Forward the element's namespace via the existing `getContainerType` helper
so these props are patched as attributes, matching the normal render path.

close #15081

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  • Loading branch information
rebeccarich and claude committed Jul 14, 2026
commit 919fcbae8cc15a4d5dd4571db1f4ea7886524700
24 changes: 24 additions & 0 deletions packages/runtime-core/__tests__/hydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2739,6 +2739,30 @@ describe('SSR hydration', () => {
}
})

test('force patch svg dynamic props with correct namespace when hydrating', () => {
__DEV__ = false
try {
const { container } = mountWithHydration(
`<svg width="24" height="24" viewBox="0 0 24 24"></svg>`,
() =>
createElementVNode(
'svg',
{ width: 48, height: 48, viewBox: '0 0 48 48' },
null,
PatchFlags.PROPS,
['width', 'height', 'viewBox'],
),
)
const el = container.firstChild as Element
expect(el.namespaceURI).toContain('svg')
expect(el.getAttribute('width')).toBe('48')
expect(el.getAttribute('height')).toBe('48')
expect(el.getAttribute('viewBox')).toBe('0 0 48 48')
} finally {
__DEV__ = true
}
})

test('only patches declared dynamic props when hydrating', () => {
const { container } = mountWithHydration(
`<div data-allow-mismatch="attribute" id="server" value="server"></div>`,
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ export function createHydrationFunctions(
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
) {
const isCustomElement = el.tagName.includes('-')
// pass the element's namespace so that e.g. readonly SVG/MathML props
// (width/height/viewBox) are patched as attributes rather than as
// DOM properties, which would throw.
const namespace = getContainerType(el)
for (const key in props) {
// check hydration mismatch
if (
Expand All @@ -520,7 +524,7 @@ export function createHydrationFunctions(
(isCustomElement && !isReservedProp(key)) ||
(dynamicProps && dynamicProps.includes(key))
) {
patchProp(el, key, null, props[key], undefined, parentComponent)
patchProp(el, key, null, props[key], namespace, parentComponent)
}
}
} else if (props.onClick) {
Expand Down
Loading