Skip to content

Commit dcc68ef

Browse files
committed
fix(hydration): do not warn against bindings w/ object values
1 parent 8d656ce commit dcc68ef

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

packages/runtime-core/__tests__/hydration.spec.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -1080,13 +1080,11 @@ describe('SSR hydration', () => {
10801080
})
10811081

10821082
test('force hydrate prop with `.prop` modifier', () => {
1083-
const { container } = mountWithHydration(
1084-
'<input type="checkbox" :indeterminate.prop="true">',
1085-
() =>
1086-
h('input', {
1087-
type: 'checkbox',
1088-
'.indeterminate': true,
1089-
}),
1083+
const { container } = mountWithHydration('<input type="checkbox">', () =>
1084+
h('input', {
1085+
type: 'checkbox',
1086+
'.indeterminate': true,
1087+
}),
10901088
)
10911089
expect((container.firstChild! as any).indeterminate).toBe(true)
10921090
})
@@ -1475,6 +1473,16 @@ describe('SSR hydration', () => {
14751473
mountWithHydration(`<select multiple></div>`, () =>
14761474
h('select', { multiple: 'multiple' }),
14771475
)
1476+
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
1477+
1478+
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
1479+
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1)
1480+
1481+
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
1482+
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
1483+
})
1484+
1485+
test('attr special case: textarea value', () => {
14781486
mountWithHydration(`<textarea>foo</textarea>`, () =>
14791487
h('textarea', { value: 'foo' }),
14801488
)
@@ -1483,11 +1491,10 @@ describe('SSR hydration', () => {
14831491
)
14841492
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
14851493

1486-
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
1494+
mountWithHydration(`<textarea>foo</textarea>`, () =>
1495+
h('textarea', { value: 'bar' }),
1496+
)
14871497
expect(`Hydration attribute mismatch`).toHaveBeenWarned()
1488-
1489-
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
1490-
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
14911498
})
14921499

14931500
test('boolean attr handling', () => {
@@ -1504,5 +1511,10 @@ describe('SSR hydration', () => {
15041511
)
15051512
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
15061513
})
1514+
1515+
test('should not warn against object values', () => {
1516+
mountWithHydration(`<input />`, () => h('input', { from: {} }))
1517+
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
1518+
})
15071519
})
15081520
})

packages/runtime-core/src/hydration.ts

+25-14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
isBooleanAttr,
2222
isKnownHtmlAttr,
2323
isKnownSvgAttr,
24+
isObject,
2425
isOn,
2526
isReservedProp,
2627
isString,
@@ -759,12 +760,17 @@ function propHasMismatch(
759760
expected = includeBooleanAttr(clientValue)
760761
} else {
761762
// #10000 some attrs such as textarea.value can't be get by `hasAttribute`
762-
actual = el.hasAttribute(key)
763-
? el.getAttribute(key)
764-
: key in el
765-
? el[key as keyof typeof el]
766-
: ''
767-
expected = clientValue == null ? '' : String(clientValue)
763+
if (el.hasAttribute(key)) {
764+
actual = el.getAttribute(key)
765+
} else if (key in el) {
766+
const serverValue = el[key as keyof typeof el]
767+
if (!isObject(serverValue)) {
768+
actual = serverValue == null ? '' : String(serverValue)
769+
}
770+
}
771+
if (!isObject(clientValue)) {
772+
expected = clientValue == null ? '' : String(clientValue)
773+
}
768774
}
769775
if (actual !== expected) {
770776
mismatchType = `attribute`
@@ -775,15 +781,20 @@ function propHasMismatch(
775781
if (mismatchType) {
776782
const format = (v: any) =>
777783
v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
778-
warn(
779-
`Hydration ${mismatchType} mismatch on`,
780-
el,
784+
const preSegment = `Hydration ${mismatchType} mismatch on`
785+
const postSegment =
781786
`\n - rendered on server: ${format(actual)}` +
782-
`\n - expected on client: ${format(expected)}` +
783-
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
784-
`in production due to performance overhead.` +
785-
`\n You should fix the source of the mismatch.`,
786-
)
787+
`\n - expected on client: ${format(expected)}` +
788+
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
789+
`in production due to performance overhead.` +
790+
`\n You should fix the source of the mismatch.`
791+
if (__TEST__) {
792+
// during tests, log the full message in one single string for easier
793+
// debugging.
794+
warn(`${preSegment} ${el.tagName}${postSegment}`)
795+
} else {
796+
warn(preSegment, el, postSegment)
797+
}
787798
return true
788799
}
789800
return false

0 commit comments

Comments
 (0)