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') { 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') { 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 * 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. * to remove it upon hydration to avoid a bug when someone resets the form value.
* @param {HTMLInputElement} dom * @param {HTMLInputElement} input
* @returns {void} * @returns {void}
*/ */
export function remove_input_attr_defaults(dom) { export function remove_input_defaults(input) {
if (hydrating) { if (!hydrating) return;
let already_removed = false;
// We try and remove the default attributes later, rather than sync during hydration. var already_removed = false;
// 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 // We try and remove the default attributes later, rather than sync during hydration.
// the idle callback, then we ensure the input defaults are cleared just before. // Doing it sync during hydration has a negative impact on performance, but deferring the
const remove_defaults = () => { // work in an idle task alleviates this greatly. If a form reset event comes in before
if (already_removed) return; // the idle callback, then we ensure the input defaults are cleared just before.
already_removed = true; var remove_defaults = () => {
const value = dom.getAttribute('value'); if (already_removed) return;
set_attribute(dom, 'value', null); already_removed = true;
set_attribute(dom, 'checked', null);
if (value) dom.value = value; // Remove the attributes but preserve the values
}; if (input.hasAttribute('value')) {
// @ts-expect-error var value = input.value;
dom.__on_r = remove_defaults; set_attribute(input, 'value', null);
queue_idle_task(remove_defaults); input.value = value;
add_form_reset_listener(); }
}
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 { head } from './dom/blocks/svelte-head.js';
export { action } from './dom/elements/actions.js'; export { action } from './dom/elements/actions.js';
export { export {
remove_input_attr_defaults, remove_input_defaults,
set_attribute, set_attribute,
set_attributes, set_attributes,
set_custom_element_data, 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 fragment = root();
var input = $.first_child(fragment); var input = $.first_child(fragment);
$.remove_input_attr_defaults(input); $.remove_input_defaults(input);
var input_1 = $.sibling($.sibling(input, true)); 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)); var button = $.sibling($.sibling(input_1, true));
@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) {
$.append($$anchor, fragment); $.append($$anchor, fragment);
} }
$.delegate(["click"]); $.delegate(["click"]);

Loading…
Cancel
Save