Agent Wranglers

Forms Required

Save each form only once

Make a double click, a second Enter or a retry save the record once. Block repeat submits in the form and reject duplicates on the server.

How to do it

While a submit is in flight, ignore further submits and show that it is saving. Keep the button focusable. In React 19, read pending from useFormStatus or isPending from useActionState. Create one idempotency key when the form opens and send it with every attempt, as idempotency keys describes. Make a new key only after a confirmed save, if the form stays open for another record. After a server-rendered post, redirect with 303, so a reload doesn't post again. In htmx, hx-disabled-elt and hx-sync block repeats.

Example

export function submitOnce(form: HTMLFormElement, send: (data: FormData, key: string) => Promise<void>) {
  const idempotencyKey = crypto.randomUUID();
  let isSending = false;
  form.addEventListener('submit', async event => {
    event.preventDefault();
    if (isSending) return;
    isSending = true;
    form.setAttribute('aria-busy', 'true');
    try { await send(new FormData(form), idempotencyKey); }
    finally { isSending = false; form.removeAttribute('aria-busy'); }
  });
}
@app.post("/api/invoices")
def create_invoice_once():
    key = request.headers.get("Idempotency-Key")
    if not key:
        return problem(400, "idempotency-key-required")
    previous = idempotency.find(current_user().id, key)
    if previous:
        return previous.response
    return idempotency.store(current_user().id, key, invoices.create_from(request.json))

How to check

A browser test clicks submit three times within 100 ms, and a second test sends the same request twice with one key. Each test creates exactly one record.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/forms/single-submit.