sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.21k stars 4.09k forks source link

<input type="reset" /> clears form instead of respecting values #8220

Open angrytongan opened 1 year ago

angrytongan commented 1 year ago

Describe the bug

Using <input type="reset" /> on a form with hardcoded values clears the form when it shouldn't.

Reproduction

Repl here: https://svelte.dev/repl/fa8b042ada144734b1814685f918f1db?version=3.55.1

Form is reset, but values shouldn't change. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/reset

Logs

No response

System Info

repl

Severity

annoyance

brunnerh commented 1 year ago

As a workaround you can use an action that calls setAttribute:

const value = (node, param) => node.setAttribute('value', param);
<input use:value={'Default value'} />

(More details)

Prinzhorn commented 1 year ago

Maybe Svelte should set the defaultValue property in these cases? The action provided by @brunnerh also works with node.defaultValue = param

What I don't understand is why that is needed when the generated code literally uses innerHTML with the blob of HTML.

gtm-nayan commented 1 year ago

What I don't understand is why that is needed when the generated code literally uses innerHTML with the blob of HTML.

Probably an inconsistency in dev mode which doesn't do the innerHTML optimization

phamduylong commented 1 year ago

could placeholder be an alternative? If you don't strictly need an initial value for those inputs it might even be the right thing to use

adiguba commented 1 year ago

Hello,

From what I see, apart from innerHTML optimization (which is not used when using a variable on the template), some specific attributes like value, checked are not added to the node's attributes, but directly in the corresponding JavaScript property.

So something like this :

<input type="text" name="{name}" value={value} />

Will generate a code like that :

    input = element("input");
    attr(input, "type", "text");
    attr(input, "name", /*name*/ ctx[0]);
    input.value = /*value*/ ctx[1];

I think that Svelte should explicitly add defaultValue in this case :

    input = element("input");
    attr(input, "type", "text");
    attr(input, "name", /*name*/ ctx[0]);
    input.value = input.defaultValue = /*value*/ ctx[1];

This apply also to defaultChecked on checkbox/radio, and defaultSelected on option (and maybe others?)

Another solution would be to use the attributes in all cases :

    input = element("input");
    attr(input, "type", "text");
    attr(input, "name", /*name*/ ctx[0]);
    attr(input, "value", /*value*/ ctx[1]);

But I don't know why Svelte treats these attributes differently, so I dont' know what the best solution.

ITenthusiasm commented 1 year ago

Just wanted to point out that the workaround mentioned in https://github.com/sveltejs/svelte/issues/8220#issuecomment-1400483958 won't work for users who don't have access to JavaScript.

I agree with adiguba that just using the raw attributes would be an excellent solution. (I think it would be the best solution.) As things stand, Svelte doesn't have a way for developers to apply a defaultValue to their inputs (in the HTML). One reason that this is significant (besides the reset bug) is that it makes things more difficult for developers who want to track the dirty/clean state of their form fields. Such a check can be done very easily with regular JS by checking input.value !== input.defaultValue (or the equivalent for checkboxes, select elements, and the like) while the user interacts with a field. But if Svelte doesn't support default values out of the box, awkward hacks have to be added in -- like calling a function (action) on every single form control with a default value when the page loads.

If Svelte is truly only going to be a superset of HTML (as it claims to be), then that needs to be 100% true -- especially for important elements like form controls. Even React supports this, and they've done a lot of awkward/abnormal things in their framework. :sweat_smile:

toymil commented 9 months ago

For radio buttons the severity is much more than "annoyance". For example this group of radio buttons:

<form>
  <fieldset>
    <legend>Please select your preferred contact method:</legend>
    <div>
      <input type="radio" id="contactChoice1" name="contact" value="email" checked />
      <label for="contactChoice1">Email</label>

      <input type="radio" id="contactChoice2" name="contact" value="phone" />
      <label for="contactChoice2">Phone</label>

      <input type="radio" id="contactChoice3" name="contact" value="mail" />
      <label for="contactChoice3">Mail</label>
    </div>
    <div>
      <button type="reset">reset</button>
    </div>
  </fieldset>
</form>

should reset to email, and the submitted FormData should always contain the contact key.

But in svelte it would reset to not selected, as a result the FormData won't contain key contact.

dummdidumm commented 5 months ago

resetting a form with hardcoded value attributes works in Svelte 5.

Same for the checked case

We should add tests though to make sure we don't regress. There's also a conversation to be had about what should happen when the default is dynamic, or when you're using bind:value in which case you cannot set a default value because the spot is already taken. I explained it more deeply in #10617

dummdidumm commented 3 months ago

Related #9230

hyunbinseo commented 1 month ago

Just wanted to point out that the workaround mentioned in #8220 (comment) won't work for users who don't have access to JavaScript.

@ITenthusiasm The workaround can work for users without JavaScript:

<script>
  /** @type {import('svelte/action').Action<HTMLElement, string>}  */
  const setValue = (node, param) => node.setAttribute('value', param);
</script>

<form>
  <!-- Important! match setValue={} and value="" -->
  <input type="text" use:setValue={'1'} value="1" />

  <!-- SSR <input type="text" value="2"> -->
  <!-- Without JavaScript: resets to '2' -->

  <!-- CSR <input type="text" value="1"> -->
  <!-- With JavaScript: resets to '1' -->
  <input type="text" use:setValue={'1'} value="2" />

  <button type="reset">Reset</button>
</form>

Though using bind:value, etc. will break stuff in CSR:

<script>
  /** @type {import('svelte/action').Action<HTMLElement, string>}  */
  const setValue = (node, param) => node.setAttribute('value', param);

  let val = 2;
  $: console.log(val); // 2
</script>

<form>
  <!-- SSR <input type="text" value="2"> -->
  <!-- CSR <input type="text" value="1"> -->
  <!-- With JavaScript: resets to '1', but `val` is not updated -->
  <input type="text" use:setValue={'1'} bind:value={val} />

  <button type="reset">Reset</button>
</form>
hyunbinseo commented 1 month ago

After a <form> reset in Svelte 4.2.18 / REPL

This is because checkbox values are rendered in the HTML markup:

<input type="text" />
<input type="checkbox" value="a" />

This was the reason why my codebase was working as expected after SvelteKit's default use:enhance.

I was not using the checked attributes, so it seemed like it followed the user-agent's behavior.