fix: ensure input value is correctly set during hydration (#12083)

* fix: ensure input value is correctly set during hydration

* fix: ensure input value is correctly set during hydration

* address feedback

* fix typos

* early return, var

* fix test

* Update packages/svelte/src/internal/client/dom/elements/attributes.js

Co-authored-by: Rich Harris <rich.harris@vercel.com>

* update changeset

* tweak names

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12086/head
Dominic Gannaway 1 year ago committed by GitHub
parent 696a4b3dae
commit 399e464b44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: preserve current input values when removing defaults

@ -2030,7 +2030,7 @@ export const template_visitors = {
}
if (needs_input_reset && node.name === 'input') {
context.state.init.push(b.stmt(b.call('$.remove_input_attr_defaults', context.state.node)));
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
}
if (needs_content_reset && node.name === 'textarea') {

@ -16,29 +16,40 @@ import { queue_idle_task, queue_micro_task } from '../task.js';
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
* to remove it upon hydration to avoid a bug when someone resets the form value.
* @param {HTMLInputElement} dom
* @param {HTMLInputElement} input
* @returns {void}
*/
export function remove_input_attr_defaults(dom) {
if (hydrating) {
let already_removed = false;
// We try and remove the default attributes later, rather than sync during hydration.
// Doing it sync during hydration has a negative impact on performance, but deferring the
// work in an idle task alleviates this greatly. If a form reset event comes in before
// the idle callback, then we ensure the input defaults are cleared just before.
const remove_defaults = () => {
if (already_removed) return;
already_removed = true;
const value = dom.getAttribute('value');
set_attribute(dom, 'value', null);
set_attribute(dom, 'checked', null);
if (value) dom.value = value;
};
// @ts-expect-error
dom.__on_r = remove_defaults;
queue_idle_task(remove_defaults);
add_form_reset_listener();
}
export function remove_input_defaults(input) {
if (!hydrating) return;
var already_removed = false;
// We try and remove the default attributes later, rather than sync during hydration.
// Doing it sync during hydration has a negative impact on performance, but deferring the
// work in an idle task alleviates this greatly. If a form reset event comes in before
// the idle callback, then we ensure the input defaults are cleared just before.
var remove_defaults = () => {
if (already_removed) return;
already_removed = true;
// Remove the attributes but preserve the values
if (input.hasAttribute('value')) {
var value = input.value;
set_attribute(input, 'value', null);
input.value = value;
}
if (input.hasAttribute('checked')) {
var checked = input.checked;
set_attribute(input, 'checked', null);
input.checked = checked;
}
};
// @ts-expect-error
input.__on_r = remove_defaults;
queue_idle_task(remove_defaults);
add_form_reset_listener();
}
/**

@ -21,7 +21,7 @@ export { element } from './dom/blocks/svelte-element.js';
export { head } from './dom/blocks/svelte-head.js';
export { action } from './dom/elements/actions.js';
export {
remove_input_attr_defaults,
remove_input_defaults,
set_attribute,
set_attributes,
set_custom_element_data,

@ -0,0 +1,16 @@
import { test } from '../../test';
export default test({
server_props: {
name: 'server'
},
props: {
name: 'browser'
},
test(assert, target) {
const input = target.querySelector('input');
assert.equal(input?.value, 'browser');
}
});

@ -0,0 +1,5 @@
<script>
const { name } = $props();
</script>
<input type="text" value={name} />

@ -16,11 +16,11 @@ export default function State_proxy_literal($$anchor) {
var fragment = root();
var input = $.first_child(fragment);
$.remove_input_attr_defaults(input);
$.remove_input_defaults(input);
var input_1 = $.sibling($.sibling(input, true));
$.remove_input_attr_defaults(input_1);
$.remove_input_defaults(input_1);
var button = $.sibling($.sibling(input_1, true));
@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) {
$.append($$anchor, fragment);
}
$.delegate(["click"]);
$.delegate(["click"]);

Loading…
Cancel
Save