Skip to main content

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.