N

Optimistic delete

The row disappears instantly on click. If the server rejects the delete, it swaps back in with an error.

#When to use

Common, reversible operations where the user shouldn't wait on the network — deleting a row, archiving a message, toggling a favorite. For destructive operations with no undo, use a confirm dialog first.

#Markup

html
<tr id="row_@Model.Id">
    <td>@Model.Name</td>
    <td>
        <form hx-post="/products/@Model.Id?handler=Delete"
              hx-target="#row_@Model.Id"
              hx-swap="outerHTML"
              hx-on::before-request="this.closest('tr').style.display = 'none'">
            <button type="submit">Delete</button>
        </form>
    </td>
</tr>
  • hx-on::before-request fires synchronously, hides the row before the request completes.
  • hx-target + hx-swap="outerHTML" mean the server response replaces the same — letting the handler either (a) return an empty body to confirm the delete, or (b) return the row markup with an error class to reverse the optimism.

#Handler

csharp
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var ok = await _products.DeleteAsync(id);
    if (!ok)
    {
        var row = await _products.GetAsync(id);
        Response.Headers["HX-Trigger"] = "{\"toast\":{\"level\":\"error\",\"message\":\"Could not delete — try again.\"}}";
        return this.PageOrPartial("_ProductRow", row);
    }
    // Success — return empty content; htmx replaces the (already hidden) row with nothing.
    return Content(string.Empty, "text/html");
}

#Gotchas

  • Empty success response: return Content(string.Empty, "text/html") rather than EmptyResult so htmx receives a 200 with empty body and replaces the target with nothing. EmptyResult produces a 204, which htmx ignores by default — leaving the hidden row in place (which still works, but is less explicit).
  • Confirm guard: pair with hx-confirm="Delete this product?" to require confirmation before the request fires.
  • Accessibility: announce the delete with aria-live="polite" on a sibling element so screen readers hear about it.