🍡 mochi
← All demos

Cancelling form submissions

Inside {@attach enhance(...)} there are two distinct cancellation points: cancel() short-circuits before the fetch is issued; controller.abort() stops a request that is already in flight. They solve different problems — they are not interchangeable.

The action sleeps 3 s to simulate a slow lookup. Each variant below uses the same action but cancels at a different point — or not at all.

cancel() — pre-flight short-circuit

The submit callback receives cancel. Calling it skips the fetch entirely — no request is sent, no result callback runs. Use this for client-side validation or any condition you can check before leaving the browser.

Type fewer than 3 characters and submit — cancel() fires, no network request

controller.abort() — in-flight cancellation

The submit callback also receives an AbortController. Calling controller.abort() stops a fetch that has already started. Here a 1.5 s timeout aborts the 3 s lookup; enhance swallows the AbortError and resets the form to idle.

Submit any query — the request aborts after 1.5 s

Plain HTML — no enhance

Without JavaScript there is nothing to cancel. The browser POSTs and waits the full 3 s, then the page re-renders with the result.

Plain POST — waits the full 3 s, page re-renders with the result

<script lang="ts">
  import CancelDemo from './CancelDemo.svelte';
  import AbortDemo from './AbortDemo.svelte';
  import PlainDemo from './PlainDemo.svelte';
  import { files } from './files.ts';

  const sources = await loadSources(files);
</script>

<p>The action sleeps 3 s to simulate a slow lookup. Each variant below uses the same action but cancels at a different point — or not at all.</p>

<h3><code>cancel()</code> — pre-flight short-circuit</h3>
<p>
  The <code>submit</code> callback receives <code>cancel</code>. Calling it skips the fetch entirely — no request is sent, no result callback runs. Use this for client-side
  validation or any condition you can check before leaving the browser.
</p>
<CancelDemo label="Type fewer than 3 characters and submit — cancel() fires, no network request" mochi:hydrate />

<h3><code>controller.abort()</code> — in-flight cancellation</h3>
<p>
  The <code>submit</code> callback also receives an
  <code>AbortController</code>. Calling <code>controller.abort()</code>
  stops a fetch that has already started. Here a 1.5 s timeout aborts the 3 s lookup; <code>enhance</code> swallows the <code>AbortError</code> and resets the form to idle.
</p>
<AbortDemo label="Submit any query — the request aborts after 1.5 s" mochi:hydrate />

<h3>Plain HTML — no <code>enhance</code></h3>
<p>Without JavaScript there is nothing to cancel. The browser POSTs and waits the full 3 s, then the page re-renders with the result.</p>
<PlainDemo label="Plain POST — waits the full 3 s, page re-renders with the result" />
Styles
<style>
  h3 {
    margin: 1.5rem 0 0.25rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-muted);
  }

  p {
    margin: 0 0 0.5rem;
    font-size: 0.9rem;
    color: var(--text-muted);
  }

  code {
    background: var(--code-bg);
    color: var(--code-accent);
    padding: 0.05rem 0.35rem;
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: 0.85rem;
  }
</style>