sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.73k stars 1.94k forks source link

Textfield is empty after form submit with use:enhance #9609

Open Beiri22 opened 1 year ago

Beiri22 commented 1 year ago

Describe the bug

I am using a form to save some string into the db. When the form loads, the load function supplies the old string from db. After sending the form (with use:enhance) the textarea is empty.

What else: -> without use:enhance it works as intended -> the variable holds the correct content -> an input type=text showed the same behaviour

Reproduction

<script lang="ts">
    import { enhance } from '$app/forms';

    export let data;
    let content = "";

    // When setting content directly, each keystroke triggers the content setting. You cannot type. This way, the reactive
    // statement does not know about content and thus only triggers with prop change.
    function setContent(s:string)
    {
        content = s
    }

    $: setContent(data.report?.content || '');
</script>

<form method="POST" use:enhance>
    <textarea name="content" cols="100" rows="20" bind:value={content} />
    <button type="submit">Absenden</button>
</form>

{JSON.stringify(data.report)} <- shows correct version of saved report

{content} <- is also updated correctly

Logs

No response

System Info

System:
    OS: Linux 6.2 Manjaro Linux
    CPU: (16) x64 Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
    Memory: 22.83 GB / 31.27 GB
    Container: Yes
    Shell: 3.6.0 - /usr/bin/fish
  Binaries:
    Node: 19.8.1 - /usr/bin/node
    Yarn: 1.22.19 - /usr/bin/yarn
    npm: 8.19.2 - /usr/bin/npm
  Browsers:
    Chromium: 111.0.5563.110
    Firefox: 111.0.1
  npmPackages:
    @sveltejs/adapter-node: ^1.0.0 => 1.2.3 
    @sveltejs/kit: ^1.0.0 => 1.15.1 
    svelte: ^3.54.0 => 3.58.0 
    vite: ^4.0.0 => 4.2.1

Severity

serious, but I can work around it

Additional Information

No response

dummdidumm commented 1 year ago

The default behavior of use:enhance is to reset the field to be in conformance with how full page reloads would work (they also clear the fields). In your case however, you're re-setting the content through the binding, which works on a full page reload but not when using use:enhance because it empties it, not knowing about the two-way binding.

I'm not sure if there's a good way to solve this case, but in the meantime you can use the reset option to not reset the form:

<form method="POST" use:enhance={() => {
  return ({ update }) => update({ reset: false });
}}>
</form>
diericx commented 1 year ago

So this is the most efficient way to implement form resets that bind values?

<script lang="ts">
export let formData = { name: '' };
</script>

<form method="POST" use:enhance={() => {
  return ({ update }) => {
    formData.name = '';
    update({ reset: false });
  }
}}>
    <input
      type="text"
      name="name"
      placeholder="Your name here"
      bind:value={formData.name}
    />
</form>
diericx commented 1 year ago

I am finding that this completely wipes my input values, which is puzzling. Note there is no binding.

<script lang="ts">
export let formData = { weight: 0 };
</script>

<form method="POST" use:enhance>
    <input
      type="number"
      name="weight"
      value={formData.weight}
    />
</form>

Even this leads to completely blank input fields on submit for numbers and dates. Note the value is simply passed in.

<form method="POST" use:enhance>
    <input
      type="number"
      name="weight"
      value={0}
    />
</form>

So when using enhance on a form with default values it is always necessary to bind all values and manually set them on submit?

Beiri22 commented 1 year ago

The default behavior of use:enhance is to reset the field to be in conformance with how full page reloads would work (they also clear the fields). In your case however, you're re-setting the content through the binding, which works on a full page reload but not when using use:enhance because it empties it, not knowing about the two-way binding.

But, when a full page reset applies the data bindings, how can just resetting be in conformance with how full page reloads would work? Seems wrong and unintuitive to me. Couldn't you just outsource the resetting part to something like use:resetOnSubmit?

elliott-with-the-longest-name-on-github commented 1 year ago

how can just resetting be in conformance with how full page reloads would work

He means how native form submits work -- submitting a POST form causes the page to reload and resets the form. In this case, however, Svelte is providing values to the form, but the enhance action doesn't have any way of knowing that (other than you telling it not to reset the form).

@dummdidumm did you mean to mark this as ready to implement? I'm not sure there's anything decided on right now (and I can't think of a good decision here).

Beiri22 commented 1 year ago

but the enhance action doesn't have any way of knowing that

Is there really no way? Couldn't we use the compile step to add some marker to the form fields, so that enhance could read that information later?

dummdidumm commented 1 year ago

@dummdidumm did you mean to mark this as ready to implement? I'm not sure there's anything decided on right now (and I can't think of a good decision here).

ready to implement as in "if you find a clever little solution to this problem then go ahead and open a PR"

fs-99 commented 1 year ago

Sounds similar to https://github.com/sveltejs/kit/issues/8513, only this time with data instead of form. (I don't mean OP's usecase, but the one without bind:)

mpost commented 1 year ago

Another approach to repopulate the form after submission from freshly loaded data is to reapply the data to a reactive variable:

<script lang="ts">
  import { enhance } from '$app/forms'

  export let form
  export let data

  $: settings = form?.errors ? form.settings : data.settings
</script>

Here we create a settings form. The data.settings provides the initial data and the data when the form is submitted successfully. The form.settings are used when the data is invalid, in which cases the form.errors flag is true and validation info is available in the form.

Now in the <form> declaration we can apply it:

 <form
      method="POST"
      use:enhance={() =>
        async ({ update }) => {
          await update({ reset: false })
          settings = form?.errors ? form.settings : data.settings
        }}
    >

The key is to update the responsive settings variable which applies the changes to the form fields. Note that we still require reset: false as the form would be cleared otherwise, leading to a blinking of the data. Creating the value of settings should be put in its own method for reusability.

The form inputs are always populated from the settings object:

<input name="name" value={settings.name} />