Skip to main content
Version: 2.x

HxSync

HxSync represents the hx-sync attribute, coordinating multiple HTMX requests by specifying how new requests interact with pending or running requests on a target element. This prevents race conditions and ensures predictable behavior when multiple events fire in quick succession.

Create an HxSync by pairing an HxTarget (which element to synchronize) with a HxSyncStrategy (how to handle conflicts):

import zio.http.htmx._

// Wait for this element's request to finish before starting a new one
HxSync(HxTarget.This, HxSyncStrategy.Queue)

// Replace a pending request with a new one
HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace)

// Drop new requests while one is in flight
HxSync(HxTarget.This, HxSyncStrategy.Drop)

// Abort the current request and start the new one immediately
HxSync(HxTarget.This, HxSyncStrategy.Abort)

Sync Strategies​

HxSyncStrategy.Queue queues new requests, executing them after the current request completes. This is the default HTMX behavior when hx-sync is not specified. Useful for ensuring requests are processed in order:

import zio.blocks.html._
import zio.http.htmx._

input(
hxPost := "/search",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Queue),
placeholder := "Search..."
)

HxSyncStrategy.Replace cancels any pending request and immediately sends the new one. Useful for searches and filters where the latest value is all that matters:

import zio.blocks.html._
import zio.http.htmx._

input(
hxPost := "/search",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
placeholder := "Live Search (only latest request sent)"
)

HxSyncStrategy.Drop discards new requests while one is in flight. Only the first request in a burst is sent; others are ignored. Useful for expensive operations that shouldn't be duplicated:

import zio.blocks.html._
import zio.http.htmx._

button(
hxPost := "/expensive-operation",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Drop),
"Process (only first request sent)"
)

HxSyncStrategy.Abort cancels any in-flight request and immediately sends the new one. The old request's response is discarded. Useful for time-sensitive operations:

import zio.blocks.html._
import zio.http.htmx._

button(
hxPost := "/time-sensitive-action",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Abort),
"Execute (cancels previous)"
)

Target Specification​

The first parameter of HxSync is an HxTarget specifying which element to synchronize. This allows coordinating requests across multiple elements:

Synchronize the current element:

Use HxTarget.This to synchronize the element that initiates the request:

import zio.blocks.html._
import zio.http.htmx._

input(
hxPost := "/search",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
placeholder := "Search..."
)

Synchronize a parent form:

Synchronize across all fields in a form using HxTarget.closest():

import zio.blocks.html._
import zio.http.htmx._

input(
hxPost := "/validate",
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue),
placeholder := "Email"
)

Synchronize multiple fields at once:

Apply the same sync strategy to multiple fields within a form:

import zio.blocks.html._
import zio.http.htmx._

form(
input(
name := "firstName",
hxPost := "/validate",
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
),
input(
name := "email",
hxPost := "/validate",
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
)
)

Rendering & Parsing​

render: String produces the literal hx-sync attribute value string:

import zio.http.htmx._

HxSync(HxTarget.This, HxSyncStrategy.Queue).render // "this:queue"
HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace).render // "closest form:replace"

HxSync.parse(value: String): Either[String, HxSync] parses a rendered string back into a typed HxSync:

import zio.http.htmx._

HxSync.parse("this:queue") // Right(HxSync(HxTarget.This, HxSyncStrategy.Queue))
HxSync.parse("closest form:replace") // Right(HxSync(HxTarget.closest("form"), HxSyncStrategy.Replace))

Common Patterns​

Synchronization strategies prevent race conditions in interactive forms. Here are practical usage patterns:

Search with Last-Value-Wins​

Use Replace to ensure only the most recent search is sent when typing quickly:

import zio.blocks.html._
import zio.http.htmx._
import scala.concurrent.duration._

input(
hxPost := "/search",
hxTrigger := HxTrigger.input.delay(300.millis),
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
hxTarget := HxTarget.css("#results"),
placeholder := "Search (only latest request sent)"
)

Ordered Form Validation​

Use Queue to validate form fields in the order they were changed, preventing race conditions:

import zio.blocks.html._
import zio.http.htmx._

form(
input(
name := "username",
hxPost := "/validate/username",
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
),
input(
name := "email",
hxPost := "/validate/email",
hxSync := HxSync(HxTarget.closest("form"), HxSyncStrategy.Queue)
)
)

Prevent Double-Submit​

Use Drop to prevent duplicate submissions when users click a button repeatedly:

import zio.blocks.html._
import zio.http.htmx._

form(
button(
hxPost := "/submit",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Drop),
"Submit (first click only)"
)
)

Abort Slow Requests​

Use Abort for requests that shouldn't queue up, replacing old requests immediately:

import zio.blocks.html._
import zio.http.htmx._

button(
hxPost := "/fetch-latest",
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Abort),
"Fetch Latest (aborts previous)"
)

Integration with Other Attributes​

HxSync works alongside trigger and timing attributes to coordinate complex request patterns:

import zio.blocks.html._
import zio.http.htmx._
import scala.concurrent.duration._

input(
hxPost := "/search",
hxTrigger := HxTrigger.input.delay(500.millis).changed,
hxSync := HxSync(HxTarget.This, HxSyncStrategy.Replace),
hxParams := HxParams.only("query"),
hxTarget := HxTarget.css("#results"),
placeholder := "Search"
)

The ToHtmxValue[HxSync] instance renders automatically, so HxSync values work seamlessly with the hxSync attribute key.