We are thrilled to announce a new HTMX App on deco hub. HTMX has been gaining popularity for its ability to create dynamic web applications without relying heavily on JavaScript. By leveraging HTML attributes, HTMX simplifies the development process, making it easier to build interactive user interfaces.
At deco.cx, we intend to support all major frameworks, including Angular, Next.JS, SvelteKit, and more. However, we started with HTMX because we see a significant potential in simplifying both the development and production of simple web pages. HTMX shines in scenarios where developers want to avoid the complexities of build steps, hot module replacement (HMR), and other tooling. With HTMX, what you write in HTML is exactly what you see in both development and production environments.
By combining HTMX with the strengths of HTML5 and CSS3, developers can deliver good user experiences and top-class performance without the overhead of downloading, parsing, and compiling heavy JavaScript libraries. Additionally, using HTMX can result in more robust web applications. Testing JavaScript across all devices and browsers (Safari, Samsung browser, etc.) is time-consuming and expensive. Having your UI rendered on the server saves you countless hours of debugging across different environments, ultimately reducing Sentry bills.
If HTMX offers so many advantages, why isn't it the standard for web application development? The primary challenge with HTMX lies in the need to create routes for each UI state, which can complicate the development process. Each interactive element or dynamic update often requires a corresponding server route, leading to a proliferation of endpoints and increased maintenance complexity.
Moreover, the web is mostly comprised of centralized servers, and computing new UI states on a centralized server can penalize peripheral users due to latency issues. This is not the case with deco.cx, where our edge-first infrastructure distributes the code globally. This ensures that computing new UI states is much cheaper and faster, thanks to our low latency and globally distributed infrastructure.
For us at deco.cx, the most difficult part of using HTMX is having to create a route for each UI state. To address this challenge, we developed a new hook called useSection
. This hook automatically creates routes for rendering your UI states without requiring developers to handle routing manually.
To demonstrate the power and simplicity of the useSection
hook, let's explore an example by building a counter component.
useSection
on deco.cxIn this guide, we'll build a simple counter component using HTMX and the useSection
hook on deco.cx.
First, let's look at the usual Preact code for this component using the useState
hook:
import { useState } from "preact/hooks";
export default function Section() {
const [count, setCount] = useState(0);
return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count - 1)}
>
<span>-</span>
</button>
<span>{count}</span>
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count + 1)}
>
<span>+</span>
</button>
</div>
);
}
To refactor this component into HTMX, we follow three simple rules:
useState
and useEffect
are removed.useState
hook are placed on the component's props.onClick
and onChange
are removedBy applying rules 1 and 2 to the Section component, we move the count
variable to the component's props, leaving us with:
export default function Section({ count }: { count: number }) {
return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count - 1)}
>
<span>-</span>
</button>
<span>{count}</span>
<button
class="btn btn-sm btn-circle btn-outline no-animation"
onClick={() => setCount(count + 1)}
>
<span>+</span>
</button>
</div>
);
}
Notice we don't need the import of useState
anymore. To apply rule number 3, we need to remove the onClick handler, however, how can we keep the interactivity? That’s where the useSection
hook is handy.
useSection
To implement the onClick
functionality, start by importing useSection
from deco/hooks/useSection.ts
. This hook lets you create a link for the current section instance and override any props this section receives. By refactoring this component, we get:
import { useSection } from "deco/hooks/useSection.ts";
export default function Section({ count = 0 }:{ count: number }) {
return (
<div class="container h-screen flex items-center justify-center gap-4">
<button
hx-get={useSection({ props: { count: count - 1 } })}
hx-target="closest section"
hx-swap="outerHTML"
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span>-</span>
</button>
<span>{count}</span>
<button
hx-get={useSection({ props: { count: count + 1 } })}
hx-target="closest section"
hx-swap="outerHTML"
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span>+</span>
</button>
</div>
);
}
Let’s dissect each part starting with the hx-get
attribute. useSection({ props: { count: count - 1 }})
creates a link for the current section instance, but overrides the count
prop. This means that if we had other props, the new count
value would be merged with the other prop values, and a link for this section would be returned. This is beneficial as the developer can now override some props inserted by the deco.cx CMS.
The hx-target
and hx-swap
combo is useful when you want to replace the whole section at deco.cx, as sections are rendered under a <section/>
element.
For 3G connections, where performing a request to increase/decrease the counter can be slow, we need to add a loading state. HTMX provides the htmx-request
indicator class for this purpose. When HTMX is performing a request, the htmx-request
class is added to the DOM. With TailwindCSS v3, we can create specific rules activated once this class is present. Here's the updated button:
<button
hx-target="closest section"
hx-swap="outerHTML"
hx-get={useSection({ props: { count: count - 1 } })}
class="btn btn-sm btn-circle btn-outline no-animation"
>
<span class="inline [.htmx-request_&]:hidden">-</span>
<span class="hidden [.htmx-request_&]:inline loading loading-spinner" />
</button>
The magic is in the [.htmx-request_&]:
selector. This tells Tailwind to create a selector that is activated whenever the htmx-request
class is present on a parent element. When performing a request, the CSS will hide the -
element and display a spinner. This approach leverages native Web APIs to enhance user experience even on slow connections. Here's the final result:
When using the useSection
hook, keep in mind that the props passed to the hook go into the final URL. Be cautious of URL size limits and try to pass small payloads like booleans and IDs.
We have a useSection
API reference available for those who want to dive deeper. Additionally, we provide a recipe for migrating from Preact to HTMX and vice versa, detailing common design patterns and how to achieve them with HTML5 and CSS3. With these tools, developers can leverage the power of HTMX and the flexibility of deco.cx to build robust, high-performance web applications.
For more information, visit our docs: https://docs.deco.cx/en/api-reference/use-section.