A better use:enhance experience with automatic state management.
Requires Svelte v5 and runes mode.
standby, submitting, and submitted statesonBeforeRequest and onAfterResponse callbackspnpm add svelte-form-enhanced -D
npm i svelte-form-enhanced -D
<script lang="ts">
  import { enhance } from '$app/forms';
  import { createFormHelper } from 'svelte-form-enhanced';
  const f = createFormHelper();
</script>
<!-- Maintains submitting state for 1 second (customizable) -->
<form method="post" use:enhance={f.submitFunction}>
  <!-- Use the submitting state to show loading UI: -->
  <button disabled={f.state === 'submitting'} class="disabled:btn-spinner">
    {f.state === 'submitting' ? 'Submitting' : 'Submit'}
  </button>
</form>
type FormState = 'standby' | 'submitting' | 'submitted';
<script lang="ts">
  import { createFormHelper } from 'svelte-form-enhanced';
  import type { SubmitFunction } from './$types.js';
  const f = createFormHelper<SubmitFunction>({
    // The `ActionResult` object is now typed.
    onAfterSubmit: ({ result }) => {},
  });
</script>
const f = createFormHelper({ /* options */ });
type Options = Partial<{
  minSubmitDuration: number; // defaults to 1000(ms)
  onBeforeSubmit: (param) => void;
  onAfterSubmit: (param) => void;
  updateOptions: { reset?: boolean; invalidateAll?: boolean };
}>;
The function provided to the use:enhance has been separated into two:
<script>
  const f = createFormHelper({
    onBeforeSubmit: () => {
      console.log('Before submit');
    },
    onAfterSubmit: async ({ update }) => {
      await update(); // default logic
      console.log('After submit');
    },
  });
</script>
<form use:enhance={f.submitFunction}></form>
<form
  use:enhance={() => {
    console.log('Before submit');
    return async ({ update }) => {
      await update(); // default logic
      console.log('After submit');
    };
  }}
></form>
use:enhancewill update theformproperty,page.formandpage.statuson a successful or invalid response, but only if the action is on the same page you’re submitting from. — SvelteKit Docs
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import { createFormHelper } from 'svelte-form-enhanced';
  import type { SubmitFunction } from './a/$types.js';
  let submittedAt = $state<Date>();
  const f = createFormHelper<SubmitFunction>({
    onAfterSubmit: async ({ result }) => {
      if (result.type !== 'success') return; // handle error
      if (!result.data) return; // type guard
      submittedAt = result.data.submittedAt;
    },
  });
</script>
<form use:enhance={f.submitFunction} method="post" action="/a">
  {#if submittedAt}
    <p>
      Last submitted at
      <time datetime={submittedAt.toISOString()}>
        {submittedAt.toLocaleTimeString()}
      </time>
    </p>
  {/if}
  <button>Submit</button>
</form>
// src/routes/a/+page.server.ts
export const actions = {
  default: () => ({ submittedAt: new Date() }),
};
When a form contains multiple submit buttons:
<script lang="ts">
  const f = createFormHelper({
    onBeforeSubmit: ({ submitter }) => {
      submitter?.classList.add('disabled:btn-spinner');
    },
    onAfterSubmit: async ({ submitter, update }) => {
      await update();
      submitter?.classList.remove('disabled:btn-spinner');
    },
  });
</script>
<form method="post" use:enhance={f.submitFunction}>
  <!-- Loading spinner only appears on the button that is clicked -->
  <button disabled={f.state === 'submitting'} formaction="?/a">A</button>
  <button disabled={f.state === 'submitting'} formaction="?/b">B</button>
</form>