Triggers, forms, and feedback

Prevent duplicate submissions

Temporarily disable the relevant control while the server processes a request and make the endpoint safe if duplicates still arrive.

Double-clicking Add on the task form can send two requests before the first response arrives. hx-disabled-elt disables a control while its request is in flight:

<form id="new-task" action="/tasks" method="post"
  hx-post="/tasks"
  hx-target="#task-list"
  hx-swap="outerHTML"
  hx-disabled-elt="find button">
  <label>Task <input name="title"></label>
  <button type="submit">Add</button>
</form>

HTMX adds the native disabled attribute before sending and removes it when the request finishes. While the POST is pending, the button is greyed out and cannot fire again.

This improves the interface but is not a data-integrity guarantee. A client can bypass the attribute. Two tabs can submit together. A retry may arrive after the first response was lost on the network.

A realistic failure: you disable the button but the server has no deduplication. A fast double-click still creates “Buy milk” and “Buy milk” if both requests arrive before either response. DevTools shows two POST /tasks with status 200. Fix: accept an idempotency key in a hidden field or header, or enforce a uniqueness constraint on title per user for a short window.

For costly operations, send an idempotency key or enforce a server-side uniqueness constraint. The endpoint should recognize the same logical operation and return its existing result instead of creating a duplicate.

I treat client-side disabling as UX polish and server-side idempotency as the real safety net. Both together cover normal users and edge cases.

Use network throttling, double-click Add deliberately, and count rows in #task-list. Then replay the same POST twice with curl. The server, not the temporary disabled state, must keep the data correct.

In DevTools, a protected form shows the button greyed out during the request. If you still see two POST /tasks entries with 200 responses, add server-side deduplication next. The attribute did its job on the client. The database layer did not.

Lesson completed