History, errors, and enhancement

Use request lifecycle events sparingly

Add cross-cutting behavior around beforeRequest, afterRequest, beforeSwap, afterSwap, and afterSettle without hiding core application logic.

HTMX exposes lifecycle events around two related processes: the HTTP request and the DOM update. They are hooks for cross-cutting behavior, not a place to hide your app.

The request side includes:

  • htmx:beforeRequest, which can cancel with preventDefault()
  • htmx:beforeSend, immediately before transmission and too late to cancel
  • htmx:afterRequest, which reports the completed request as successful or failed

The response and DOM side includes:

  • htmx:beforeSwap, which can change shouldSwap, target, or swap behavior
  • htmx:afterSwap, after new DOM is inserted
  • htmx:afterSettle, after settling finishes

Do not use afterRequest as a substitute for a DOM event. With swap or settle delays, the request can finish before the visual update you care about.

Use the narrowest event that matches the concern. When the task form returns a 422, focus #form-errors in htmx:afterSwap, not in afterRequest. The error summary does not exist until the swap inserts it. Waiting on afterRequest and querying the DOM is a race you will lose under slow networks.

A global listener is appropriate for shared observability:

document.addEventListener('htmx:afterRequest', event => {
  console.debug('HTMX request', {
    url: event.detail.xhr.responseURL,
    successful: event.detail.successful
  })
})

Per-element listeners are fine for one-off behavior, such as focusing an error summary on a specific form. Global listeners belong to logging, analytics, and shared error chrome. If you find yourself branching on URL inside a global handler, the logic probably belongs in the server response or in attributes on that element.

Avoid moving domain logic into global events. Authorization, validation, and state transitions belong on the server. Method, URL, target, and swap should remain visible near the HTML. Otherwise the page becomes difficult to understand without tracing hidden listeners.

Try this on your task list: log htmx:beforeSwap and note when shouldSwap is false for a 422. That one listener documents your validation contract better than scattered comments.

Lesson completed